tomar riesgos precios para nombre muscular masa los inyecciones hialuronico gluteos con colageno aumento aumentar aumenta argentina acido c++ g++ language-lawyer clang++ standard-layout

c++ - riesgos - Diseño estándar y relleno de cola



nombre de inyecciones para aumentar los gluteos (5)

Aquí hay un caso concreto que demuestra por qué el segundo caso no puede reutilizar el relleno:

union bob { FooBeforeBase a; FooBefore b; }; bob.b.value = 3.14; memset( &bob.a, 0, sizeof(bob.a) );

Esto no puede borrar bob.b.value .

union bob2 { FooAfterBase a; FooAfter b; }; bob2.b.value = 3.14; memset( &bob2.a, 0, sizeof(bob2.a) );

Este es un comportamiento indefinido.

David Hollman recientemente tuiteó el siguiente ejemplo (que he reducido ligeramente):

struct FooBeforeBase { double d; bool b[4]; }; struct FooBefore : FooBeforeBase { float value; }; static_assert(sizeof(FooBefore) > 16); //---------------------------------------------------- struct FooAfterBase { protected: double d; public: bool b[4]; }; struct FooAfter : FooAfterBase { float value; }; static_assert(sizeof(FooAfter) == 16);

Puede examinar el diseño en clang en godbolt y ver que la razón por la que cambió el tamaño es que en FooBefore , el value del miembro se coloca en el desplazamiento 16 (manteniendo una alineación completa de 8 de FooBeforeBase ) mientras que en FooAfter , el value del miembro se coloca en offset 12 (efectivamente utilizando el FooAfterBase de cola de FooAfterBase ).

Para mí es claro que FooBeforeBase es de diseño estándar, pero FooAfterBase no (porque sus miembros de datos no estáticos no tienen el mismo control de acceso, [class.prop]/3 ). Pero, ¿de qué se trata la FooBeforeBase estándar de FooBeforeBase que requiere este respeto de los bytes de relleno?

Tanto gcc como clang reutilizan el relleno de FooAfterBase , terminando con sizeof(FooAfter) == 16 . Pero MSVC no lo hace, terminando con 24. ¿Hay un diseño requerido según el estándar y, si no es así, por qué gcc y clang hacen lo que hacen?

Hay algo de confusión, así que solo para aclarar:

  • FooBeforeBase es de diseño estándar
  • FooBefore no lo es (tanto ella como una clase base tienen miembros de datos no estáticos, similares a E en este ejemplo )
  • FooAfterBase no lo es (tiene miembros de datos no estáticos de diferente acceso)
  • FooAfter no lo es (por las dos razones anteriores)

Aquí hay un caso similar a la respuesta de nm.

Primero, tengamos una función que FooBeforeBase un FooBeforeBase :

void clearBase(FooBeforeBase *f) { memset(f, 0, sizeof(*f)); }

Esto está bien, ya que clearBase obtiene un puntero a FooBeforeBase , piensa que como FooBeforeBase tiene un diseño estándar, por lo tanto, la configuración es segura.

Ahora, si haces esto:

FooBefore b; b.value = 42; clearBase(&b);

No esperas, que clearBase borrará b.value , ya que b.value no es parte de FooBeforeBase . Pero, si FooBefore::value se colocara en el relleno de la cola de FooBeforeBase , también se FooBeforeBase .

¿Hay un diseño requerido según el estándar y, de no ser así, por qué gcc y clang hacen lo que hacen?

No, no se requiere relleno de cola. Es una optimización, lo que hacen gcc y clang.


La respuesta a esta pregunta no proviene de la norma, sino del Itanium ABI (por lo que gcc y clang tienen un comportamiento, pero msvc hace otra cosa). Que ABI define un diseño , cuyas partes relevantes a los fines de esta pregunta son:

Para propósitos internos a la especificación, también especificamos:

  • dsize (O): el tamaño de los datos de un objeto, que es el tamaño de O sin relleno de cola.

y

Ignoramos el relleno de la cola de los POD porque una versión anterior de la norma no nos permitía usarla para nada más y porque a veces permite una copia más rápida del tipo.

Donde la ubicación de miembros que no sean clases de base virtual se define como:

Comience en dsize (C) de desplazamiento, incrementado si es necesario para alineación con nvalign (D) para clases base o para alinear (D) para miembros de datos. Coloque D en este desplazamiento a menos que [... no sea relevante ...].

El término POD ha desaparecido del estándar C ++, pero significa diseño estándar y trivialmente copiable. En esta pregunta, FooBeforeBase es un POD. El Itanium ABI ignora el relleno de la cola; por dsize(FooBeforeBase) tanto, dsize(FooBeforeBase) es 16.

Pero FooAfterBase no es un POD (se puede copiar de forma trivial, pero no es de diseño estándar). Como resultado, el relleno de la cola no se ignora, por lo que dsize(FooAfterBase) es solo 12, y el float puede ir allí.

Esto tiene consecuencias interesantes, como lo señaló Quuxplusone en una respuesta relacionada , los implementadores también suelen asumir que el relleno de la cola no se reutiliza, lo que causa estragos en este ejemplo:

#include <algorithm> #include <stdio.h> struct A { int m_a; }; struct B : A { int m_b1; char m_b2; }; struct C : B { short m_c; }; int main() { C c1 { 1, 2, 3, 4 }; B& b1 = c1; B b2 { 5, 6, 7 }; printf("before operator=: %d/n", int(c1.m_c)); // 4 b1 = b2; printf("after operator=: %d/n", int(c1.m_c)); // 4 printf("before std::copy: %d/n", int(c1.m_c)); // 4 std::copy(&b2, &b2 + 1, &b1); printf("after std::copy: %d/n", int(c1.m_c)); // 64, or 0, or anything but 4 }

Aquí, = hace lo correcto (no anula el relleno de cola de B ), pero copy() tiene una optimización de biblioteca que se reduce a memmove() , que no se preocupa por el relleno de cola porque asume que no existe.


FooBefore tampoco es std-layout; dos clases declaran miembros de datos no estáticos ( FooBefore y FooBeforeBase ). Por lo tanto, el compilador puede colocar arbitrariamente algunos miembros de datos. De ahí surgen las diferencias en las diferentes cadenas de herramientas. En una jerarquía de diseño estándar, en la mayoría de los casos una clase (ya sea la clase más derivada o, como mucho, una clase intermedia) declarará miembros de datos no estáticos.


FooBefore derived; FooBeforeBase src, &dst=derived; .... memcpy(&dst, &src, sizeof(dst));

Si el miembro de datos adicional se colocó en el agujero, memcpy lo habría sobrescrito.

Como se señala correctamente en los comentarios, el estándar no requiere que esta invocación memcpy funcione. Sin embargo, el Itanium ABI está diseñado aparentemente con este caso en mente. Quizás las reglas ABI se especifican de esta manera para hacer que la programación en varios idiomas sea un poco más robusta, o para preservar algún tipo de compatibilidad hacia atrás.

Las reglas relevantes de ABI se pueden encontrar aquí .

Una respuesta relacionada se puede encontrar .com/a/51334730/775806 (esta pregunta podría ser un duplicado de esa).