The program below compiles and runs without warnings with gcc:
#include <iostream>
struct A { int a, b; };
int main() {
A x = {.a = 42, .b = x.a}; // <-- b is initialized from x.a
std::cout << x.a << ' ' << x.b << std::endl;
}
42 42
x is essentially used during its initialization. This is very handy for cases where .a is initialized by a large expression.
Is this a legal expression in C++, and am I guaranteed to always get the right answer?
[dcl.init.aggr]
The initializations of the elements of the aggregate are evaluated in the element order.
That is, all value computations and side effects associated with a given element are sequenced before those of any element that follows it in order.
Thus, x.a has been initialised before initialisation of x.b. So far, so good.
[basic.life]
The lifetime of an object or reference is a runtime property of the object or reference. A variable is said to have vacuous initialization if it is default-initialized and, if it is of class type or a (possibly multi-dimensional) array thereof, that class type has a trivial default constructor. The lifetime of an object of type T begins when:
storage with the proper alignment and size for type T is obtained, and
its initialization (if any) is complete (including vacuous initialization) ([dcl.init]),
The lifetime of x.a has begun although the lifetime of x has not.
Similarly, before the lifetime of an object has started ... any glvalue that refers to the original object may be used but only in limited ways.
For an object under construction or destruction, see [class.cdtor].
Otherwise, such a glvalue refers to allocated storage ([basic.stc.dynamic.allocation]), and using the properties of the glvalue that do not depend on its value is well-defined.
The program has undefined behavior if:
the glvalue is used to access the object, or
...
"Access" is defined:
[defns.access]
⟨execution-time action⟩ read or modify the value of an object
Note 1: Only objects of scalar type can be accessed.
Reads of scalar objects are described in [conv.lval] and modifications of scalar objects are describred in [expr.ass], [expr.post.incr], and [expr.pre.incr].
Attempts to read or modify an object of class type typically invoke a constructor or assignment operator; such invocations do not themselves constitute accesses, although they may involve accesses of scalar subobjects.
— end note]
According to this note, accessing x.a is not an access of the class object named by x. Thus, x.a being initialised should be sufficient and the example is well-defined and OK.
Minor problem: Notes are not normative.
Edit: I removed quotes to rules that apply to objects "under/during construction" with the assumption that those do not apply to aggregates being initialised.
P.S. Perhaps for clarity, or even just to not make readers of the code to be concerned about the legality, consider using an intermediate variable:
int temp = 42;
A x = {.a = temp, .b = temp};
P.P.S Designated initialisers were first introduced to standard C++ in C++20. They are not in C+17.
Related
Inspired by this question.
We know that global variables with non-constexpr initializers undergo two different "initializations":
First, the "static initialization", which zero-initializes them.
Second, the "dynamic initialization", which uses the user-provided initializer.
Which of those initializations starts the variable lifetime? [basic.life] is being surprisingly unhelpful:
The lifetime of an object ... begins when: ... its initialization (if any) is complete
I see several options:
The last initialization starts the lifetime.
The first initialization starts the lifetime.
Each successive initialization destroys the existing object and creates a new one in its place.
(1) Would make the most sense, but it would make the static initialization of an object that will later be dynamically initialized mostly useless.
(2) Would have interesting effects. E.g. the static init order fiasco is suddenly not UB (by itself) anymore.
(3) Would be very weird.
Indeed, the standard is vague by not specifying how exactly the quoted rule "initialization (if any) is complete" applies to multiple stages of initialization, each of which being referred to as "initialization". And this leaves room for multiple interpretations.
This footnote (non-normative) implies that the lifetime begins only after dynamic initialisation is complete:
[basic.life]
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated 26 ...
26 For example, before the dynamic initialization of an object with static storage duration ([basic.start.dynamic]).
As the other answer makes its argument solely on a non-normative footnote, I will offer a counter-argument based on non-normative note. Not that I have any belief in this interpretation over the other, but I think the standard is in non-normative conflict here, at best.
Note 2 of [basic.start.static]/3 gives an example of unspecified, not undefined, behavior where an implementor (adhering to [basic.start.static]/3) is permitted to perform initialization of a static/thread storage duration variable even if it is not required to be initialized during static initialization (could be during dynamic initialization). The key here being the note's example of why the value of obj1 is unspecified: namely that the value of the object which is used to initialize it may be "fully initialized" or "merely zero-initialized":
[Note 2: As a consequence, if the initialization of an object obj1 refers to an object obj2 potentially requiring dynamic
initialization and defined later in the same translation unit, it is
unspecified whether the value of obj2 used will be the value
of the fully initialized obj2 (because obj2 was statically
initialized) or will be the value of obj2 merely
zero-initialized. For example,
inline double fd() { return 1.0; }
extern double d1;
double d2 = d1; // unspecified:
// either statically initialized to 0.0 or
// dynamically initialized to 0.0 if d1 is
// dynamically initialized, or 1.0 otherwise
double d1 = fd(); // either initialized statically or dynamically to 1.0
— end note]
Particularly the comment from the example code snippet:
[d2] either statically initialized to 0.0 or dynamically initialized to 0.0 if d1 is dynamically initialized, or 1.0 otherwise.
Implying that the "otherwise" case, namely where d1 is (dynamically) initialized from the "merely zero-initialized" d2 is not undefined.
I would say, the dynamic initialization begins the lifetime of that object. Since the lifetime is a property defined as that
[basic.life#1]
The lifetime of an object or reference is a runtime property of the object or reference.
Zero-initialization could be said that occurs at a compile-time that is the reason why static initialization requires a constant expression as its initialization, which could be evaluated at compile-time.
The initialization in the normative rule you cited arguably refers to all dynamic initializations that can start the lifetime of an object(at runtime).
int i = i;
int main() {
int a = a;
return 0;
}
int a = a surely has undefined behaviour (UB), and more details on it is in
Is reading an uninitialized value always an undefined behaviour? Or are there exceptions to it?.
But what about int i = i? In C++ we are allowed to assign nonconstant values to globals. i is declared and zero initialized (since it has file scope) before the declaration is encountered. In which case we are assigning 0 to it later in the definition.
Is it safe to say this does not have UB?
Surprisingly, this is not undefined behavior.
Static initialization [basic.start.static]
Constant initialization is performed if a variable or temporary object
with static or thread storage duration is constant-initialized. If
constant initialization is not performed, a variable with static
storage duration or thread storage duration is zero-initialized.
Together, zero-initialization and constant initialization are called
static initialization; all other initialization is dynamic
initialization. All static initialization strongly happens before any
dynamic initialization.
Important parts bold-faced. "Static initialization" includes global variable initialization, "static storage duration" includes global variables, and the above clause is applicable here:
int i = i;
This is not constant-initialization. Therefore, zero-initialization is done according to the above clause (for basic integer types zero-initialization means, unsurprising, that it's set to 0). The above clause also specifies that zero initialization must take place before dynamic initialization.
So, what happens here:
i is initialized to 0.
i is then dynamically initialized, from itself, so it still remains 0.
The behavior might be undefined for i, since depending on how you read the standard, you could be reading i before its lifetime starts.
[basic.life]/1.2
... The lifetime of an object of type T begins when:
— its initialization (if any) is complete ...
As mentioned in the other answer, i is initialized twice: first zero-initialized statically, then initialized with i dynamically.
Which initialization starts the lifetime? The first one or the final one?
The standard is being vague, and there are conflicting notes in it (albeit all of them are non-normative). Firstly, there is a footnote in [basic.life]/6 (thanks #eerorika) that explicitly says that the dynamic initialization starts the lifetime:
[basic.life]/6
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated26
...
26) For example, before the dynamic initialization of an object with static storage duration ...
This interpretation makes the most sense to me, because otherwise it would
be legal to access class instances before they undergo dynamic
initialization, before they could estabilish their invariants (including the standard library classes defined by the standard).
There's also a conflicting note in [basic.start.static]/3, but that one is older than the one I mentioned above.
It appears to me int i = i; has undefined behavior, is not caused by the indeterminate value. The term indeterminate value is designed for the objects that have automatic or dynamic storage duration.
[basic.indet#1]
When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced ([expr.ass]).
[basic.indet#2]
If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases...
In your example, the object named i has a static storage duration, hence it is not within the extent of talking about indeterminate value. And, such an object has a zero-initialization that happens before any dynamic initialization as per [basic.start.static#2]
Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. All static initialization strongly happens before ([intro.races]) any dynamic initialization.
Hence, its initial value is zero. when i is used as an initializer to initialize itself. which is a dynamic initialization and it obeys [dcl.init].
Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression.
It violates the rule in [basic.lifetime]
The program has undefined behavior if:
the glvalue is used to access the object, or
Consider a simple union with a changed "active member":
union U {
int i;
char *p;
};
U u = { 1 };
u.p = 0;
Is there any revision of the C++ standard that can properly define what happens here?
In particular, what is u.p semantically? It's a lvalue at compile time, but what does its evaluation refer to at run time?
Can a pointer object exist in u before it's assigned to?
Can objects exist before their lifetime even begins?
Can two scalar objects (of distinct types) coexist at the same time at the same address?
u.p refers to storage allocated for an object whose lifetime has not yet started, as permitted by [basic.life]/7: "Before the lifetime of an object has started but after the storage which the object will occupy has been allocated... any glvalue that refers to the original object may be used but only in limited ways."
Then there's the special magic by which an assignment to a union member starts the lifetime of the object:
[class.union]/5 When the left operand of an assignment operator involves a member access expression ([expr.ref]) that nominates a union member, it may begin the lifetime of that union member, as described below...
In an assignment expression of the form E1 = E2 that uses either the built-in assignment operator ([expr.ass]) or a trivial assignment operator ([class.copy.assign]), for each element X of S(E1), if modification of X would have undefined behavior under [basic.life], an object of the type of X is implicitly created in the nominated storage; no initialization is performed and the beginning of its lifetime is sequenced after the value computation of the left and right operands and before the assignment.
given a class who's only member is a char[10], that has no inheritance nor virtual members, that has a constructor that does not mention the array in any way (such that it gets default-initialization -> no initialization, like so:
class in_place_string {
char data[10];
static struct pre_initialized_type {} pre_initialized;
in_place_string(pre_initialized_type) {} //This is the constructor in question
in_place_string() :data() {} //this is so you don't yell at me, not relevent
};
Is it defined behavior to placement-new this class into a buffer that already has data, and then read from the array member?
int main() {
char buffer[sizeof(in_place_string)] = "HI!";
in_place_string* str = new(buffer) in_place_string(in_place_string::pre_initialized);
cout << str->data; //undefined behavior?
}
I'm pretty sure it's not well defined, so I'm asking if this is implementation defined or undefined behavior.
You're not performing a reinterpret_cast (which wouldn't be safe, since the class has non-trivial initialization); you're creating a new object whose member is uninitialized.
Performing lvalue->rvalue conversion on an uninitialized object gives an indeterminate value and undefined behavior. So is the object uninitialized?
According to 5.3.4 all objects created by new-expression have dynamic storage duration. There's no exception for placement new.
Entities created by a new-expression have dynamic storage duration
And then 8.5 says
If no initializer is specified for an object, the object is default-initialized. When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced (5.17). [ Note: Objects with static or thread storage duration are zero-initialized, see end note ] If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases:
and the following cases permit only unsigned char, and even then the value is not useful.
In your case the new object has dynamic storage duration (!) and its members for which no initialization is performed have indeterminate value. Reading them gives undefined behavior.
I think the relevant clause is 8.5 [dcl.init] paragraph 12:
If no initializer is specified for an object, the object is default-initialized. When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced (5.17). [ Note: Objects with static or thread storage duration are zero-initialized, see 3.6.2. —end note ] If an indeterminate value is produced by an evaluation, the behavior is undefined except in the
following cases:
If an indeterminate value of unsigned narrow character type (3.9.1) is produced by the evaluation of:
the second or third operand of a conditional expression (5.16),
the right operand of a comma expression (5.18),
the operand of a cast or conversion to an unsigned narrow character type (4.7, 5.2.3, 5.2.9, 5.4), or
a discarded-value expression (Clause 5), then the result of the operation is an indeterminate value.
If an indeterminate value of unsigned narrow character type is produced by the evaluation of the right operand of a simple assignment operator (5.17) whose first operand is an lvalue of unsigned narrow character type, an indeterminate value replaces the value of the object referred to by the left operand.
If an indeterminate value of unsigned narrow character type is produced by the evaluation of the initialization expression when initializing an object of unsigned narrow character type, that object is initialized to an indeterminate value.
I don't think any of the exception applies. Since the value is read before being initialized after the object is constructed, I think the code results in undefined behavior.
Is the following safe?
*(new int);
I get output as 0.
It’s undefined because you’re reading an object with an indeterminate value. The expression new int() uses zero-initialisation, guaranteeing a zero value, while new int (without parentheses) uses default-initialisation, giving you an indeterminate value. This is effectively the same as saying:
int x; // not initialised
cout << x << '\n'; // undefined value
But in addition, since you are immediately dereferencing the pointer to the object you just allocated, and do not store the pointer anywhere, this constitutes a memory leak.
Note that the presence of such an expression does not necessarily make a program ill-formed; this is a perfectly valid program, because it sets the value of the object before reading it:
int& x = *(new int); // x is an alias for a nameless new int of undefined value
x = 42;
cout << x << '\n';
delete &x;
This is undefined behavior(UB) since you are accessing an indeterminate value, C++14 clearly makes this undefined behavior. We can see that new without initializer is default initialized, from the draft C++14 standard section 5.3.4 New paragraph 17 which says (emphasis mine going forward):
If the new-initializer is omitted, the object is default-initialized
(8.5). [ Note: If no initialization is performed, the object has an
indeterminate value. —end note ]
for int this means an indeterminate value, from section 8.5 paragraph 7 which says:
To default-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9), the default constructor (12.1) for T is called (and
the initialization is ill-formed if T has no default constructor or overload resolution (13.3) results in an
ambiguity or in a function that is deleted or inaccessible from the context of the initialization);
— if T is an array type, each element is default-initialized;
— otherwise, no initialization is performed.
we can see from section 8.5 that producing an indeterminate value is undefined:
If no initializer is specified for an object, the object is
default-initialized. When storage for an object with automatic or
dynamic storage duration is obtained, the object has an indeterminate
value, and if no initialization is performed for the object, that
object retains an indeterminate value until that value is replaced
(5.17). [ Note: Objects with static or thread storage duration are
zero-initialized, see 3.6.2. — end note
If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases
and all the exceptions have to do with unsigned narrow char which int is not.
Jon brings up an interesting example:
int& x = *(new int);
it may not be immediately obvious why this is not undefined behavior. The key point to notice is that is is undefined behavior to produce a value but in this case no value is produced. We can see this by going to section 8.5.3 References, which covers initialization of references and it says:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
— If the reference is an lvalue reference and the initializer expression
— is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
and goes on to say:
then the reference is bound to the initializer expression lvalue in
the first case [...][ Note: The usual lvalue-to-rvalue (4.1),
array-to-pointer (4.2), and function-to-pointer (4.3) standard
conversions are not needed, and therefore are suppressed, when such
direct bindings to lvalues are done. —end note ]
It is possible that a computer has "trapping" values of int: invalid values, such as a checksum bit which raises a hardware exception when it doesn't match its expected state.
In general, uninitialized values lead to undefined behavior. Initialize it first.
Otherwise, no, there's nothing wrong or really unusual about dereferencing a new-expression. Here is some odd, but entirely valid code using your construction:
int & ir = * ( new int ) = 0;
…
delete & ir;
First of all, Shafik Yaghmour gave references to the Standard in his answer. That is the best, complete and authoritative answer. None the less, let me try to give you specific examples that should illustrate the aforementioned points.
This code is safe, well-formed and meaningful:
int *p = new int; // ie this is a local variable (ptr) that points
// to a heap-allocated block
You must not, however, dereference the pointer as that results in undefined behavior. IE you may get 0x00, or 0xFFFFFFFF, or the instruction pointer (aka RIP register on Intel) may jump to a random location. The computer may crash.
int *p = new int;
std::cout << *p; // Very, bad. Undefined behavior.
Run-time checkers such as Valgrind and ASan will catch the issue, flag it and crash with a nice error message.
It is, however, perfectly fine to initialize the memory block you had allocated:
int *p = new int;
*p = 0;
Background info: this particular way of writing the specification is very useful for performance, as it is prohibitively expensive to implement the alternative.
Note, as per the Standard references, sometimes the initialization is cheap, so you can do the following:
// at the file scope
int global1; // zero-initialized
int global2 = 1; // explicitly initialized
void f()
{
std::cout << global1;
}
These things go into the executable's sections (.bss and .data) and are initialized by the OS loader.