sobrecarga - programacion orientada a objetos c++ ejemplos
¿Por qué puede ser peligroso usar esta estructura POD como clase base? (5)
Tuve esta conversación con un colega, y resultó ser interesante. Digamos que tenemos la siguiente clase de POD
struct A {
void clear() { memset(this, 0, sizeof(A)); }
int age;
char type;
};
clear
tiene la intención de borrar todos los miembros, estableciendo en 0
(bytes). ¿Qué podría salir mal si usamos A
como clase base? Hay una fuente sutil para los errores aquí.
Además de las otras notas, sizeof
es un operador en tiempo de compilación, por lo que clear()
no pondrá a cero ningún miembro agregado por clases derivadas (excepto lo señalado debido a la rareza del relleno).
No hay nada realmente "sutil" sobre esto; memset
es una cosa horrible de usar en C ++. En los raros casos en los que realmente puede llenar la memoria con ceros y esperar un comportamiento sano, y realmente necesita llenar la memoria con ceros, y poner todo en cero a través de la lista de inicializadores de forma civilizada es de alguna manera inaceptable, use std::fill
en lugar.
El método clear
de la clase base solo establecerá los valores de los miembros de la clase.
De acuerdo con las reglas de alineación, al compilador se le permite insertar relleno para que el siguiente miembro de datos se produzca en el límite alineado. Por lo tanto, habrá relleno después del miembro de datos de type
. El primer miembro de datos del descendiente ocupará esta ranura y estará libre de los efectos de memset
, ya que el tamaño de la clase base no incluye el tamaño del descendiente. Tamaño del padre! = Tamaño del niño (a menos que el niño no tenga miembros de datos). Ver rebanar .
El embalaje de estructuras no es parte del estándar de lenguaje. Con suerte, con un buen compilador, el tamaño de una estructura empaquetada no incluye bytes adicionales después de la última. Aun así, un descendiente empaquetado que hereda de un padre empaquetado debe producir el mismo resultado: el padre establece solo los miembros de datos en el padre.
En teoría, el compilador puede disponer las clases base de manera diferente. C ++ 03 §10 párrafo 5 dice:
Un subobjeto de clase base puede tener un diseño (3.7) diferente del diseño de un objeto más derivado del mismo tipo.
Como mencionó StackedCrooked , esto puede suceder si el compilador agrega relleno al final de la clase base A
cuando existe como su propio objeto, pero el compilador no puede agregar ese relleno cuando es una clase base. Esto causaría que A::clear()
sobrescriba los primeros bytes de los miembros de la subclase.
Sin embargo, en la práctica, no he podido lograr que esto suceda con GCC o Visual Studio 2008. Usando esta prueba:
struct A
{
void clear() { memset(this, 0, sizeof(A)); }
int age;
char type;
};
struct B : public A
{
char x;
};
int main(void)
{
B b;
printf("%d %d %d/n", sizeof(A), sizeof(B), ((char*)&b.x - (char*)&b));
b.x = 3;
b.clear();
printf("%d/n", b.x);
return 0;
}
Y modificando A
, B
, o ambos para que sean ''empaquetados'' (con #pragma pack
en VS y __attribute__((packed))
en GCC), no podría sobrescribir bx
en ningún caso. Las optimizaciones fueron habilitadas. Los 3 valores impresos para los tamaños / compensaciones siempre fueron 8/12/8, 8/9/8, o 5/6/5.
Es probable que el compilador agregue bytes de relleno a A. Por lo tanto, sizeof(A)
extiende más allá del char type
(hasta el final del relleno). Sin embargo, en caso de herencia, el compilador podría no agregar los bytes rellenados. Así que la llamada a memset
sobrescribirá parte de la subclase.
En resumen : me parece que el único problema potencial es que no puedo encontrar ninguna información sobre los "bytes de relleno" garantizados en los estándares C89, C2003 ... ¿Tienen algún comportamiento extraordinariamente volátil o de solo lectura? No puedo encontrar incluso lo que significa el término "relleno de bytes" por los estándares ...
Detallado :
Para objetos de tipos POD, está garantizado por el estándar C ++ 2003 que:
- Cuando memoriza el contenido de su objeto en una matriz de caracteres char o unsigned char, y luego lo guarda de nuevo en su objeto, el objeto mantendrá su valor original.
garantizado que no habrá relleno al principio de un objeto POD
puede romper las reglas de C ++ sobre: goto sentencia, vida útil
Para C89 también existen algunas garantías sobre las estructuras:
Cuando se usa para una mezcla de estructuras de unión si las estructuras tienen el mismo principio, entonces los primeros componentes tienen matemáticas perfectas
El tamaño de las estructuras en C es igual a la cantidad de memoria para almacenar todos los componentes, el lugar debajo del relleno entre los componentes, el relleno debajo de las siguientes estructuras
En C los componentes de la estructura se dan direcciones. Existe una garantía de que los componentes de la dirección están en orden ascendente. Y la dirección del primer componente coincide con la dirección de inicio de la estructura. Independientemente de cuál endian la computadora donde se ejecuta el programa
Entonces me parece que tales reglas son apropiadas para C ++ también, y todo está bien. Realmente creo que, en el nivel de hardware, nadie le restringirá la escritura en bytes de relleno para objetos no constantes.