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 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, whe 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!

No comments:

Post a Comment