Showing posts with label initialize-list. Show all posts
Showing posts with label initialize-list. Show all posts

Apr 8, 2025

C-style array members

A while ago, I was dealing with a class with a c-style array member. Questions arose about how copy- and move- constructions works for such class. In this post, I will have a look how c-style array members are handled in such scenarios like copy- and move constructions. I will have a small discussion around initializations and take a look in the memory, using Visual studio 2019, C++14, 32 bit. 

Okay, let's start by define a struct and have some discussion about it.
struct SimpleStruct
{
   int myInts[4]
}
This is a simple struct. It's also meeting the criterias for being a POD as well as an aggregate. In the C++98 era, the typical initialization of such struct would be:
SimpleStruct s = {{1, 2, 3, 4}};
In C++11, the list initialization was introduced. We can use the list initialization syntax, in this case the copy list initialization version, which will perform aggregate initialization, since SimpleStruct is an aggregate.
SimpleStruct s = {{1, 2, 3, 4}};
The other form of list initialization, the direct list initialization, will also perform aggregate initialization. This syntax was not available in the C++98 era.
SimpleStruct s{{1, 2, 3, 4}};
Aggregate initialization typically performs a memberwise initialization. Let's look in memory, when the statement above has executed. Remember that 0xcc is the opcode for debug break.
0x00B5F9B0  00 00 00 00 5e ef 18 7b 74 60 26 7b cc cc cc cc  ....^ï.{t`&{ÌÌÌÌ
0x00B5F9C0  01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00  ................
0x00B5F9D0  cc cc cc cc f4 f9 b5 00 03 32 43 00 01 00 00 00  ÌÌÌÌôùµ..2C.....
Before continuing with the copy/move construction, let's see what std::is_trivial, std::is_standard_layout and std:: is_pod returns when taking SimpleStruct as a template argument.
int main()
{
    std::cout << std::boolalpha;
    std::cout << "SimpleStruct is trivial: " << std::is_trivial<SimpleStruct>::value << std::endl;
    std::cout << "SimpleStruct is standard layout: " << std::is_standard_layout<SimpleStruct>::value << std::endl;
    std::cout << "SimpleStruct is pod: " << std::is_pod<SimpleStruct>::value << std::endl;
}
SimpleStruct is trivial: true
SimpleStruct is standard layout: true
SimpleStruct is pod: true
Now. let's see what is happening when we want to copy an instance of SimpleStruct. Remember, since SimpleStruct does not define any special member functions, all of them are implicitly defined, including the copy- and move- constructor.
SimpleStruct s{{1, 2, 3, 4}};
SimpleStruct other{s};
Again, we are using direct list initialization, this time resulting in a call to the copy constructor. The copy constructor is implicitly defined and performs a memberwise copying. 

Let's look into the memory.
0x00F3FBA0  bc fb f3 00 cc cc cc cc 01 00 00 00 02 00 00 00  .ûó.ÌÌÌÌ........
0x00F3FBB0  03 00 00 00 04 00 00 00 cc cc cc cc cc cc cc cc  ........ÌÌÌÌÌÌÌÌ
0x00F3FBC0  01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00  ................
0x00F3FBD0  cc cc cc cc f4 fb f3 00 03 32 8d 00 01 00 00 00  ÌÌÌÌôûó..2......
Above, at 0x00F3FBC0, we have s, and at 0x00F3FBA8, we have other.

Finally, let's investigate the move construction.
SimpleStruct s{{1, 2, 3, 4}};
SimpleStruct other{std::move(s)};
It was this statement that orginally confused me. I had a poor understanding about memberwise moving for a class, which contains a c-style array and imagine a copy of a pointer, since c-style arrays, in general, decays into pointers. The move constructor is implicitly defined and performs a memberwise move. What does it mean for a c-style array? Well, applying std::move on a single int would imply a copy and applying std::move on a c-style array would imply a copy of the whole array.

Let's look into the memory.
0x010FF6C0  cc cc cc cc 01 00 00 00 02 00 00 00 03 00 00 00  ÌÌÌÌ............
0x010FF6D0  04 00 00 00 cc cc cc cc cc cc cc cc 01 00 00 00  ....ÌÌÌÌÌÌÌÌ....
0x010FF6E0  02 00 00 00 03 00 00 00 04 00 00 00 cc cc cc cc  ............ÌÌÌÌ
Above, at 0x010FF6DC, we have s, and at 0x010FF6C4, we have other.

According to this StackOverflow post, which is discussing the 'c-style array in a struct/class scenario', we have this statement from the C++ standard:

if the subobject is an array, each element is assigned, in the manner appropriate to the element type;

This is what we have seen above in the memory dumps.

You are welcome to leave comments, complaints or questions!

Mar 8, 2015

Aggregate initialization

A couple of days ago, I ran into some legacy code, which basically said
Person person = {};
I hadn't really been dealing with this type of initialization before, so I had to look it up. I learned it is called aggregate initialization, and that's the topic for this post. I'm using WinDbg, Visual Studio Express 2010 and compiling in debug mode. Note also that this discussion does not take C++11 into account.

Let's say you have a struct like this.
struct Person
{
   char* name;
   int age;
   bool employed;
};
Before proceeding, we need to know what an aggregate is.
Here is the definition from C++03.
An aggregate is an array or a class (clause 9) with no user-declared constructors (12.1), no private or protected non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).
So the struct Person is an aggregate, which is a condition for the aggregate initialization to work.

So what does this piece of code do?
Person person = {};
I've learned that this initialization says (in this particular case) that all members in the struct will be zero-initialized.

Before proceeding with the aggregate initialization, just a quick note what may happen if you forget to initialize the struct.
The code below, will result in a struct with indeterminate values, and we get a runtime error message when executing the program.

Person person;
printf("%s, %d, %d", person.name, person.age, person.employed);



Well, back to aggregate initialization.

If we execute this piece of code, we will see that the members are zero-initialized.
#include <cstdio>

struct Person
{
   char* name;
   int age;
   bool employed;
};

int main()
{
   Person person = {};
   printf("%s, %d, %d", person.name, person.age, person.employed);

   getchar();
   return 0;
}


Execution of the program above
Above, we say that we initialize Person with an empty initialize-list.

The struct Person can be initialized using a complete initialize-list, see the example below.
 
Person person = { "Kent", 24, true };
printf("%s, %d, %d", person.name, person.age, person.employed);

Execution of the program above

It is also possible to only initialize some of the members, the remaining will be zero-initialized. In the example below, the name- and age-member will be initialized, while the employed-member will be zero-initialized.

Person person = { "Kent", 24 };
printf("%s, %d, %d", person.name, person.age, person.employed);

Execution of the program above

Before ending this post, let's check out the stack from the last example. In the screenshot below, we have just executed the initialization.


Let's clarify what's going on above. The first instruction of interest is at the virtual address 0x009213DE in the disassembly view. As you can see, the struct's Person's name-member is initialized with a 32 bit pointer. This pointer (within the red box) points to the string literal "Kent" (which probably is within a read-only section in the memory).

Further, we see that the struct's Person's age-member is initialized with 18h, i.e. 24.

Finally, the last member in the struct Person, the employed-member, is zero-initialized, using the xor eax, eax instruction.

The stack is shown to the left, as you can see within the blue box, the current stack frame (EBP) start at virtual address 0x0025FA14. The boxes in the stack view shows where the Person's member are located and its values.

You are welcome to leave comments, complaints or questions!