Initialization Evaluation Procedure

In the following program:

#include <iostream> struct I { int i; I(){i=2;} I(int _i){i=_i;} }; int a[3] = {a[2] = 1}; int aa[3][3] = {aa[2][2] = 1}; IA[3] = {A[2].i = 1}; I AA[3][3] = {AA[2][2].i = 1}; int main(int argc, char **argv) { for (int b : a) std::cout << b << ' '; std::cout << '\n'; for (auto &bb : aa) for (auto &b : bb) std::cout << b << ' '; std::cout << '\n'; for (auto &B : A) std::cout << Bi << ' '; std::cout << '\n'; for (auto &BB : AA) for (auto &B : BB) std::cout << Bi << ' '; std::cout << '\n'; return 0; } 

Output signal

 1 0 0 1 0 0 0 0 0 0 0 1 1 2 2 1 2 2 2 2 2 2 2 2 

from http://ideone.com/1ueWdK with clang3.7

but the result:

 0 0 1 1 0 0 0 0 0 0 0 1 1 2 2 1 2 2 2 2 2 2 2 2 

on http://rextester.com/l/cpp_online_compiler_clang also with clang 3.7.

On my own ubuntu, gcc 6.2 gives an internal compiler error for the construct int aa[3][3] = {aa[2][2] = 1} .

I assume that this behavior is undefined, but cannot find the final operator in the standard.

The question arises:

Is there a defined procedure for evaluating side effects for assignment in the list of initializers (for example, a[2] = 1 ) and initialization of the actual element of the array (for example, a[2] ) defined in the standard?

Is it explicitly specified as specific or undefined? Or does it become undefined just because it is not explicitly defined?

Or did the constructor define or undefined behavior for a reason other than the evaluation order?

+5
source share
1 answer

Let's start with the simplest case:

 IA[3] = {A[2].i = 1}; I AA[3][3] = {AA[2][2].i = 1}; 

Both of them are UB, due to a violation of [basic.life]. You get access to the value of the object before it begins life. I does not have a trivial default constructor and therefore cannot be evacuated. Therefore, the lifetime of an object begins only after the completion of the constructor. The elements of array A have not yet been created when you access the elements of this array.

Therefore, you call UB, referring to an object not yet built.

Now two other cases are more complicated:

 int a[3] = {a[2] = 1}; int aa[3][3] = {aa[2][2] = 1}; 

See, int allows "empty initialization" as defined by [basic.life] / 1. Storage was purchased for A and aa . Therefore, int a[3] is a valid array of int objects, although aggregate initialization has not yet begun. Thus, access to an object and even setting its state is not UB.

The order of operations here is fixed. Even pre-C ++ 17, the initialization of initializer list items is sequenced before aggregation initialization is called, as specified in [dcl.init.list] / 4. Elements in the aggregate that are not listed in the initialization list here will be populated as if in typename{} constructs. int{} means value-initialize int , resulting in 0.

Therefore, even if you set a[2] and aa[2][2] , they must be immediately overwritten by unit initialization.

Therefore, all these compilers are erroneous. The answer should be:

 1 0 0 1 0 0 0 0 0 0 0 0 

Now provided, it's all very stupid, and you shouldn't do that. But from a purely linguistic point of view, this is clearly defined behavior.

+4
source

Source: https://habr.com/ru/post/1260021/


All Articles