简体   繁体   中英

Array of structs on heap not properly initialized

I thought I knew how to deal with memory management in c++ but this confused me:

Consider the following code:

struct A {
    int i;
};

int main(int argc, char* argv[]) {
    A a{ 5 }; //Constructs an A object on the stack
    A* b = new A{ 7 }; //Constructs an A object on the heap and stores a pointer to it in b
    A* c = new A[] { //Construct an array of A objects on the heap and stores a pointer to it in c
        { 3 },
        { 4 },
        { 5 },
        { 6 }
    };
    std::cout << "a: " << a.i << "\n"; //Prints 'a: 5'
    std::cout << "b: " << b->i << "\n"; //Prints 'b: 7'
    std::cout << "c: " << c[0].i << "; " << c[1].i << "; " << c[2].i << "; " << c[3].i << "\n"; 
    //Prints 'c: -33686019; -1414812757; -1414812757; -1414812757'

    delete b;
    delete[] c;
    return 0;
}

I don't understand why the last print-out of c prints those weird numbers. If I add a constructor to A like so:

struct A {
    A(int i) : i{i} {}
    int i;
};

Then the output of the last print-out becomes:

'c: 3; 4; 5; 6'

as it should be. But now delete[] c; will give me a runtime error (not an exception it seems) that says MyGame.exe has triggered a breakpoint. (I'm working in VS2013).

Furthermore, if I change the line A* c = new A[] { to A* c = new A[4] { the error disappears and everything works as expected.

So my questions are: Why the weird numbers? Won't the A objects in the array get properly constructed somehow if I don't define a constructor? And why do I need to specify the array size explicitly even though it will compile and link just fine without? Initializing arrays on the stack this way does not give me a runtime error (I tested it to be sure).

This is an error:

A* c = new A[] { {3}, {4}, {5}, {6} };

You must put the dimension inside the [] . With new the array dimension cannot be deduced from the initializer list.

Putting 4 in here makes your code work correctly for me.

Your compiler apparently has an "extension" that treats new A[] as new A[1] .

If you compile in standard mode (with gcc or clang, -std=c++14 -pedantic ), which is always a good idea, the compiler will tell you about things like this. Treat warnings as errors unless you are really sure they are not errors :)

Why the weird numbers?

Because no memory was allocated to back them. The pointer is pointing at Crom knows what. That structure should not compile.

Won't the A objects in the array get properly constructed somehow if I don't define a constructor?

Without a constructor all of the members will be initialized to their defaults. int 's and most Plain Old Datatypes have no defined default value. In a typical implementation they get whatever value happens to already be in their allocated memory block. If a member object is of a type that doesn't default constructor and is unable to make one, you get a compiler error.

And why do I need to specify the array size explicitly even though it will compile and link just fine without?

It shouldn't compile, mismatch between the size of the array (unspecified and an error unto itself) and the number of elements in the initializer list, so the compiler has a bug. Linker is not involved at this point.

Initializing arrays on the stack this way does not give me a runtime error (I tested it to be sure).

In the static version the compiler can count the number of elements in initialization list. Why the dynamic version with new can't, gotta say I have no good answer. You'd think it would be a simple bit of counting that initializer list, so there's something deeper preventing it. The folk who debated and then approved the standard either never considered allocating a dynamic array that way or couldn't find a good way to make it work in all cases. Same reason variable length arrays still aren't in the standard.

"And why do I need to specify the array size explicitly even though it will compile and link just fine without? It shouldn't compile, ...." To be clear: If I add the constructor to A and run it, it runs just fine up until the delete[] statement. Only then it crashes but cout << c[0] works as 'expected'

This is because you are unlucky. That constructor is writing into memory that your program owns, but didn't allocate to c . Printing those values works, but whatever was supposed to be in memory at that point has been overwritten. This will probably cause your program to crash sooner or later. This time it's later.

My suspicions, and this is guesswork based on specific because you've ventured far into the realms of the undefined, are the crash on delete[] is because

A* c = new A[]

Allocated A[1] and assigned it to c rather than failing to compile. c has one A to work with. The initializer list tries to stuff in 4 and writes 3 into c[0] and the 4,5, and 6 over the heap control information that delete needs to put the data back. All looks good until delete tries to use that overwritten information.

Oh and this:"Without a constructor all of the members will be initialized to their defaults. int's and most Plain Old Datatypes have no defined default value.". For structs a user defined ctor seems optional because you can initialize a struct by providing arguments corresponding to its data fields.

A struct has a much more permissive attitude toward data encapsulation than a class and defaults to public access where a class defaults to private . I've never tried it, but I'm betting that you can use the same struct trick to init all the public members of a class.

OK. Just tried it. Works in GCC 4.8.1. Not going to make that claim in general without looking it up in the standard. Got to get a copy of it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM