c++ - Diferencia entre clase y estructura con respecto al relleno y la herencia
c++11 gcc (2)
Todo lo siguiente se realizará en GCC 9.1 utilizando
Compiler Explorer
, en x86-64, usando
-O3
.
Tengo este codigo
struct Base {
Base() {}
double foo;
int bar;
};
struct Derived : public Base {
int baz;
};
int main(int argc, char** argv)
{
return sizeof(Derived);
}
Devuelve correctamente
16
, como es de esperar, 8 bytes para
foo
, y 4 bytes para la
bar
y 4 bytes para
baz
.
Esto funciona solo porque
Derived
hereda de la
Base
y, por lo tanto, no tiene que rellenar después de la
bar
debido a que
Derived
es un tipo único que contiene elementos tanto de
Base
como
Derived
.
Tengo dos preguntas, como a continuación:
Primera pregunta
Si elimino el constructor explícito de
Base() {}
, comienza a devolver
24
, en lugar de
16
.
Es decir, añade relleno después de
bar
y
baz
.
No puedo explicar por qué tener un constructor predeterminado explícito es diferente a tener un constructor predeterminado implícito.
Segunda pregunta
Si luego cambio la
struct
a
class
para
Base
, vuelve a cambiar a
16
.
No puedo explicar esto tampoco.
¿Por qué los modificadores de acceso cambiarían el tamaño de la estructura?
Con su clase Base obtendrá 4 bytes de relleno de cola, y lo mismo con la clase Derivada, por eso normalmente debería ser de
24 bytes
total para el tamaño de
Derived
.
Se convierte en 16 bytes, porque su compilador puede reutilizar el relleno de la cola .
Sin embargo, la reutilización del relleno de la cola es problemática con los
tipos de
POD
(todos los miembros públicos, constructores predeterminados, etc.), porque rompe las suposiciones comunes que un programador haría.
(Entonces, básicamente, cualquier compilador sano no reutilizará el relleno de cola para los tipos de pod)
Supongamos que los compiladores utilizarían la
tail padding reuse
para los tipos de POD:
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!
}
Al agregar un constructor explícito a la clase Base, o al cambiar las palabras clave de
struct
a
class
, la clase
Derived
ya no satisface la definición de POD y, por lo tanto, la reutilización del relleno de cola no ocurre.
Todo esto se reduce a si su tipo es un agregado o no. Con
struct Base {
Base() {}
double foo;
int bar;
};
struct Derived : public Base {
int baz;
};
Base
no es un agregado debido al constructor.
Cuando elimina el constructor, hace que
Base
un agregado que, al
.com/q/47914612/560648
, significa que gcc no "optimizará" el espacio y el objeto derivado no usará los de la base. relleno de la cola.
Cuando cambias el código a
class Base {
double foo;
int bar;
};
struct Derived : public Base {
int baz;
};
foo
y
bar
ahora son privados (porque las clases tienen accesibilidad privada de manera predeterminada), lo que de nuevo significa que
Base
ya no es un agregado, ya que los agregados no pueden tener miembros privados.
Esto significa que estamos de vuelta a cómo funciona el primer caso.