Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused about c++ base class layout

Here is my code

#include <bits/stdc++.h>


class A{
    int val;
    char c;
};
class B:public A{
    char val;
};

struct C{
    int val;
    char c;
};
struct D:public C{
    char val;
};


int main()
{
    std::cout<<sizeof(B)<<std::endl; //8
    std::cout<<sizeof(D)<<std::endl; //12

}

Why class have different alignment with struct


*** Dumping AST Record Layout
   0 | class A
   0 |   int val
   4 |   char c
     | [sizeof=8, dsize=5, align=4
     |  nvsize=5, nvalign=4]


*** Dumping AST Record Layout
   0 | class B
   0 |   class A (base)
   0 |     int val
   4 |     char c
   5 |   char val
     | [sizeof=8, dsize=6, align=4
     |  nvsize=6, nvalign=4]


*** Dumping AST Record Layout
   0 | struct C
   0 |   int val
   4 |   char c
     | [sizeof=8, dsize=8, align=4
     |  nvsize=8, nvalign=4]


*** Dumping AST Record Layout
   0 | struct D
   0 |   struct C (base)
   0 |     int val
   4 |     char c
   8 |   char val
     | [sizeof=12, dsize=9, align=4
     |  nvsize=9, nvalign=4]

like image 378
vyyd Avatar asked Apr 30 '20 03:04

vyyd


2 Answers

In the struct case consider this program:

void f(C& cx)
{
    cx.c = 'x';
}

int main()
{
    D d{};
    d.D::val = 'y';
    f(d);
    std::cout << d.D::val << '\n';
}

This code has to output y.

On your system, the A and C structs have size of 8 since there is a member with size 4 and a char, and the struct must be correctly aligned for its largest member. These structs have 4 bytes of int, 1 byte of char, and 3 padding bytes.

The assignment cx.c = 5; is allowed to modify the padding (any struct assignment can modify the struct padding). Therefore that padding cannot be used to store base class elements.

However no analogous example is possible with A and B because the data members of A are private. There cannot be a function void f(A& ax) { ax.c = 'x'; } so this concern does not arise, and the compiler can get away with using the padding area of A to store derived class members.


NB: Neither class is standard layout due to having data members in both the base and derived classes.

like image 94
M.M Avatar answered Sep 30 '22 08:09

M.M


Adding to @M.M answer, looks like even if you have public constructor and setter member function for class A, the compiler still stores the class B data members in padding area of class A (I was trying to force the compiler to not to use tail padding of class A but couldn't succeed).

A note can be found in class.mem/19 saying:

[ Note: Non-static data members of a (non-union) class with the same access control and non-zero size ([intro.object]) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified. Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions ([class.virtual]) and virtual base classes ([class.mi]). — end note ]

Adding more from this answer:

The standard requires members with the same access control to be grouped together in memory. That grouping decides how the object gets padded so changing it can/will change the size of the object.

And more from this answer:

The dsize, nvsize, and nvalign of these types are defined to be their ordinary size and alignment. These properties only matter for non-empty class types that are used as base classes. We ignore tail padding for PODs because an early version of the standard did not allow us to use it for anything else and because it sometimes permits faster copying of the type.

Thus, in your first example, A is not a POD for layout purposes and its tail padding can be used for B::val, but in your second example, it is a POD, and its tail padding cannot be reused.

#include <iostream>


class A {
    int val;
    char c;
public:
    A(int a, char b): val(a), c(b)
    {

    }
public:
    void setC(int a)
    {
        c = a;
    }
    char getC(void) const
    {
        return c;
    }
};

class B: public A {
    char val;
public:
    B(void): A(1,'2'), val('2')
    {

    }
public:
    char getVal(void) const
    {
        return val;
    }
};

struct C {
    int val;
    char c;
};
struct D: public C {
    char val;
};


int main()
{
    B a;
    a.setC(2370);
    std::cout << a.getVal() << " & " << a.getC() << std::endl;
    std::cout << sizeof(B) << std::endl; // 8
    std::cout << sizeof(D) << std::endl; // 12
    return 0;
}

Outputs:

2 & B
8
12 

To learn about memory order and alignment for classes, see this.

like image 43
abhiarora Avatar answered Sep 30 '22 08:09

abhiarora