Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between class and struct with regards to padding and inheritance

Tags:

c++

gcc

c++11

All of the below will be done on GCC 9.1 using Compiler Explorer, in x86-64, using -O3.

I have this code:

struct Base {     Base() {}     double foo;     int bar; };  struct Derived : public Base {     int baz; };  int main(int argc, char** argv) {     return sizeof(Derived); } 

https://godbolt.org/z/OjSCZB

It correctly returns 16, as I would expect, 8 bytes for foo, and 4 bytes for bar and 4 bytes for baz. This works only because Derived inherits from Base and so it does not have to pad after bar due to Derived being a single type containing both Base and Derived elements.

I have two questions, as below:

First question

If I remove the explicit constructor of Base() {}, it starts returning 24, instead of 16. i.e. it adds padding after bar and baz.

https://godbolt.org/z/0gaN5h

I can't explain why having an explicit default constructor is any different to having an implicit default constructor.

Second question

If I then change struct to class for Base, it changes back to returning 16. I can not explain this either. Why would the access modifiers change the size of the structure?

https://godbolt.org/z/SCYKwL

like image 968
Salgar Avatar asked Jul 19 '19 14:07

Salgar


People also ask

Can struct be used in inheritance?

A struct cannot inherit from another kind of struct, whereas classes can build on other classes. You can change the type of an object at runtime using typecasting. Structs cannot have inheritance, so have only one type. If you point two variables at the same struct, they have their own independent copy of the data.

What is the difference between a class and a struct?

Difference between Structs and Classes: Struct are value types whereas Classes are reference types. Structs are stored on the stack whereas Classes are stored on the heap. Value types hold their value in memory where they are declared, but a reference type holds a reference to an object in memory.

Can struct be used as base class for inheritance?

Yes, struct can inherit from class in C++. In C++, classes and struct are the same except for their default behaviour with regards to inheritance and access levels of members.

What's the main difference between a struct and class in C++?

The only difference between a struct and class in C++ is the default accessibility of member variables and methods. In a struct they are public; in a class they are private.


2 Answers

This all boils down to whether your type is an aggregate or not. With

struct Base {     Base() {}     double foo;     int bar; };  struct Derived : public Base {     int baz; }; 

Base is not an aggregate because of the constructor. When you remove the constructor, you make Base an aggregate which, per Adding a default constructor to a base class changes sizeof() a derived type, means gcc won't "optimize" for space and the derived object won't use the base's tail padding.

When you change the code to

class Base {     double foo;     int bar; };  struct Derived : public Base {     int baz; }; 

foo and bar are now private (becauses classes have private accessibility by default) which again means Base is no longer an aggregate as aggregates are not allowed to have private members. This means we are back to how the first case works.

like image 154
NathanOliver Avatar answered Sep 23 '22 23:09

NathanOliver


With your Base class you will get 4 bytes of tail padding, and the same with the Derived class, that's why it should normally be 24 bytes total for the size of Derived.

It becomes 16 bytes, because your compiler is able to do tail padding reuse.

However tail padding reuse is problematic with POD types (all members public, defaulted constructor, etc...), because it breaks common assumptions a programmer would make. (So basically any sane compiler won't do tail padding reuse for pod types)

Let's pretend compilers would use the tail padding reuse for POD types:

struct Base {     double foo;     int bar; };  struct Derived : Base {     int baz; };  int main(int argc, char** argv) {     // if your compiler would reuse the tail padding then the sizes would be:     // sizeof(Base) == 16     // sizeof(Derived) == 16      Derived d;     d.baz = 12;     // trying to zero *only* the members of the base class,     // but this would zero also baz from derived, not very intuitive     memset((Base*)&d, 0, sizeof(Base));      printf("%d", d.baz); // d.baz would now be 0! }  

When adding an explicit constructor to the Base class, or by changing the struct keywords to class, the Derived class doesn't satisfy the POD definition anymore and therefore tail padding reuse doesn't happen.

like image 31
Turtlefight Avatar answered Sep 23 '22 23:09

Turtlefight