3

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.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • I'm currently too lazy to chase through the standard: I'm pretty sure it is undefined behavior, e.g., because there is no guarantee that `str` starts at the same address as `buffer` (and I guess you meant to write `str->data`). That said, one reason members are left uninitialized is that implementations _can_ define what it means and you may be able to access, e.g., memory mapped I/O by overlying the memory area in question with an object: implementations can define behavior the standard leaves undefined but they don't have to (as for implementation defined behavior). – Dietmar Kühl Dec 22 '14 at 02:19
  • 1
    @DietmarKühl How is there no guarantee? It's the first member of the class, placement constructed at the beginning of the buffer. Where else could `data` be? – Barry Dec 22 '14 at 02:21
  • 2
    @DietmarKühl: As much as I hate to contemplate correcting you of all people, I'm pretty sure it's well defined that placement new constructs at the addressed passed in. – Mooing Duck Dec 22 '14 at 02:27
  • @MooingDuck: it is certainly known that I'm not always correct. Seems I need to chase through the standard to find out... – Dietmar Kühl Dec 22 '14 at 02:29
  • `in_place_string::pre_initialized` is invalid, you need a member-access operator to access a non-static data member. Perhaps you left out a `static` ? – Ben Voigt Dec 22 '14 at 02:32
  • 2
    It seems, there isn't much wiggle room according to 5.3.4 [expr.new] paragraph 11: "... That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array. ..." Since the size matches the object side, the object needs to live exactly in the prescribe area. I'm still pretty sure the code is undefined... – Dietmar Kühl Dec 22 '14 at 02:35
  • 1
    @Barry: It's rather relevant that this is a *standard-layout* class, otherwise the first member might not be at the beginning. – Ben Voigt Dec 22 '14 at 02:38
  • 1
    @Ben: yes but actually it doesn't really matter if the address of the object and the address of the member coincide: since the member is accessed through the object, any adjustment is automatically made. The thing which is important, though, is that its address is known and for standard-layout classes I think the first member has to start at the start of the object. – Dietmar Kühl Dec 22 '14 at 02:40
  • 1
    @DietmarKühl: The pre-existing data `"HI!"` is aligned to the beginning of the object. If the class were not standard-layout, that might correspond to padding (or a vtable pointer, or whatever debug data the compiler wants to add) rather than the array member. – Ben Voigt Dec 22 '14 at 02:42
  • 5
    related to [placement-new-and-uninitialized-pod-members](http://stackoverflow.com/questions/14659752/placement-new-and-uninitialized-pod-members) – Jarod42 Dec 22 '14 at 02:47
  • @Jarod42: Related indeed, but different because that question involves a type with trivial initialization, and here there is a non-trivial default constructor (although it isn't called on this execution path) – Ben Voigt Dec 22 '14 at 03:22
  • @BenVoigt: Makes me glad I had the foresight to mention that. – Mooing Duck Dec 22 '14 at 06:12

2 Answers2

4

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Fascinating, I thought all `unsigned char` exceptions also applied to `char`. I'm wondering if converting the buffer to `unsigned char` would fix things, but 4.1/2 is a little thick – Mooing Duck Dec 22 '14 at 03:12
  • 1
    @MooingDuck: No, the exception for `unsigned char` just makes the indeterminate value propagate without causing UB. The value remains unusable. – Ben Voigt Dec 22 '14 at 03:19
  • It's weird to me that placement new causes an initialized array to become "uninitialized" – Mooing Duck Dec 22 '14 at 03:19
  • 1
    @MooingDuck: It creates a brand-new object. Your "initialized array" hasn't become uninitialized, it has ceased to exist (its storage has been reused). – Ben Voigt Dec 22 '14 at 03:20
1

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.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380