Namespaces
Variants

Objects and alignment

From cppreference.net

Los programas en C crean, destruyen, acceden y manipulan objetos.

Un objeto en C es una región de almacenamiento de datos en el entorno de ejecución, cuyos contenidos pueden representar valores (un valor es el significado de los contenidos de un objeto, cuando se interpretan como poseedores de un tipo específico).

Cada objeto tiene

Los objetos se crean mediante declaraciones , funciones de asignación , literales de cadena , literales compuestos , y mediante expresiones no lvalue que devuelven estructuras o uniones con miembros de arreglo .

Contenidos

Representación de objeto

Excepto para los campos de bits , los objetos están compuestos de secuencias contiguas de uno o más bytes, cada uno consistiendo en CHAR_BIT bits, y pueden copiarse con memcpy a un objeto de tipo unsigned char [ n ] , donde n es el tamaño del objeto. El contenido del array resultante se conoce como representación del objeto .

Si dos objetos tienen la misma representación de objeto, se comparan igual (excepto si son NaNs de punto flotante). Lo contrario no es cierto: dos objetos que se comparan igual pueden tener diferentes representaciones de objeto porque no todos los bits de la representación de objeto necesitan participar en el valor. Dichos bits pueden usarse para relleno para satisfacer requisitos de alineación, para comprobaciones de paridad, para indicar representaciones de trampa, etc.

Si una representación de objeto no representa ningún valor del tipo de objeto, se conoce como trap representation . Acceder a una trap representation de cualquier forma distinta a leerla a través de una expresión lvalue de tipo carácter es comportamiento indefinido. El valor de una estructura o unión nunca es una trap representation incluso si algún miembro particular lo es.

Para los objetos de tipo char , signed char , y unsigned char , se requiere que cada bit de la representación del objeto participe en la representación del valor y cada posible patrón de bits representa un valor distinto (no se permiten bits de relleno, bits trampa o representaciones múltiples).

Cuando los objetos de tipos enteros ( short , int , long , long long ) ocupan múltiples bytes, el uso de esos bytes está definido por la implementación, pero las dos implementaciones dominantes son big-endian (POWER, Sparc, Itanium) y little-endian (x86, x86_64): una plataforma big-endian almacena el byte más significativo en la dirección más baja de la región de almacenamiento ocupada por el entero, una plataforma little-endian almacena el byte menos significativo en la dirección más baja. Consulte Endianness para más detalles. Véase también el ejemplo a continuación.

Aunque la mayoría de las implementaciones no permiten representaciones trampa, bits de relleno o múltiples representaciones para tipos enteros, hay excepciones; por ejemplo un valor de un tipo entero en Itanium puede ser una representación trampa .

Tipo efectivo

Cada objeto tiene un effective type , que determina qué lvalue accesses son válidos y cuáles violan las reglas de strict aliasing.

Si el objeto fue creado por una declaración , el tipo declarado de ese objeto es el effective type del objeto.

Si el objeto fue creado por una función de asignación (incluyendo realloc ), no tiene tipo declarado. Dicho objeto adquiere un tipo efectivo de la siguiente manera:

  • La primera escritura a ese objeto a través de un lvalue que tiene un tipo distinto al tipo carácter, momento en el que el tipo de ese lvalue se convierte en el tipo efectivo de este objeto para esa escritura y todas las lecturas posteriores.
  • memcpy o memmove copian otro objeto en ese objeto, o copian otro objeto en ese objeto como un arreglo de tipo carácter, momento en el que el tipo efectivo del objeto fuente (si lo tenía) se convierte en el tipo efectivo de este objeto para esa escritura y todas las lecturas posteriores.
  • Cualquier otro acceso al objeto sin tipo declarado, el tipo efectivo es el tipo del lvalue utilizado para el acceso.

Alias estricto

Dado un objeto con tipo efectivo T1, usar una expresión lvalue (típicamente, desreferenciar un puntero) de un tipo diferente T2 es comportamiento indefinido, a menos que:

  • T2 y T1 son tipos compatibles .
  • T2 es una versión calificada cvr de un tipo que es compatible con T1.
  • T2 es una versión con signo o sin signo de un tipo que es compatible con T1.
  • T2 es un tipo agregado o tipo unión que incluye uno de los tipos mencionados anteriormente entre sus miembros (incluyendo, recursivamente, un miembro de un subagregado o unión contenida).
  • T2 es un tipo carácter ( char , signed char , o unsigned char ).
int i = 7;
char* pc = (char*)(&i);
if (pc[0] == '\x7') // el aliasing a través de char está permitido
    puts("Este sistema es little-endian");
else
    puts("Este sistema es big-endian");
float* pf = (float*)(&i);
float d = *pf; // Comportamiento indefinido: el lvalue float *p no puede usarse para acceder a un int

Estas reglas controlan si, al compilar una función que recibe dos punteros, el compilador debe emitir código que vuelva a leer uno después de escribir a través de otro:

// int* y double* no pueden alias
void f1(int* pi, double* pd, double d)
{
    // la lectura de *pi puede realizarse solo una vez, antes del bucle
    for (int i = 0; i < *pi; i++)
        *pd++ = d;
}
struct S { int a, b; };
// int* y struct S* pueden aliasing porque S es un tipo agregado con un miembro de tipo int
void f2(int* pi, struct S* ps, struct S s)
{
    // la lectura de *pi debe ocurrir después de cada escritura a través de *ps
    for (int i = 0; i < *pi; i++)
        *ps++ = s;
}

Tenga en cuenta que el calificador restrict puede utilizarse para indicar que dos punteros no hacen alias incluso si las reglas anteriores permiten que lo hagan.

Tenga en cuenta que el type-punning también puede realizarse a través del miembro inactivo de una union .

Alineación

Cada tipo de objeto completo object type tiene una propiedad llamada requisito de alineación , que es un valor entero de tipo size_t que representa el número de bytes entre direcciones sucesivas en las que se pueden asignar objetos de este tipo. Los valores de alineación válidos son potencias integrales no negativas de dos.

El requisito de alineación de un tipo puede consultarse con _Alignof (hasta C23) alignof (desde C23) .

(desde C11)

Para satisfacer los requisitos de alineación de todos los miembros de una estructura, se puede insertar relleno después de algunos de sus miembros.

#include <stdalign.h>
#include <stdio.h>
// los objetos de struct S pueden asignarse en cualquier dirección
// porque tanto S.a como S.b pueden asignarse en cualquier dirección
struct S
{
    char a; // tamaño: 1, alineación: 1
    char b; // tamaño: 1, alineación: 1
}; // tamaño: 2, alineación: 1
// los objetos de struct X deben asignarse en límites de 4 bytes
// porque X.n debe asignarse en límites de 4 bytes
// porque el requisito de alineación de int es (normalmente) 4
struct X
{
    int n;  // tamaño: 4, alineación: 4
    char c; // tamaño: 1, alineación: 1
    // tres bytes de relleno
}; // tamaño: 8, alineación: 4
int main(void)
{
    printf("sizeof(struct S) = %zu\n", sizeof(struct S));
    printf("alignof(struct S) = %zu\n", alignof(struct S));
    printf("sizeof(struct X) = %zu\n", sizeof(struct X));
    printf("alignof(struct X) = %zu\n", alignof(struct X));
}

Salida posible:

sizeof(struct S) = 2
alignof(struct S) = 1
sizeof(struct X) = 8
alignof(struct X) = 4

Cada tipo de objeto impone su requisito de alineación en cada objeto de ese tipo. La alineación más débil (menor) es la alineación de los tipos char , signed char , y unsigned char , y es igual a 1 . La alineación fundamental más estricta (mayor) de cualquier tipo está definida por la implementación y es igual a la alineación de max_align_t (desde C11) .

Las alineaciones fundamentales son compatibles para objetos de todas las clases de duración de almacenamiento.

Si la alineación de un objeto se hace más estricta (mayor) que max_align_t usando _Alignof (hasta C23) alignof (desde C23) , tiene un requisito de alineación extendida . Un tipo struct o union cuyo miembro tiene alineación extendida es un tipo sobrealineado . Está definido por la implementación si los tipos sobrealineados son compatibles, y su compatibilidad puede ser diferente en cada tipo de duración de almacenamiento .

Si un tipo struct o union S no tiene ningún miembro de un tipo sobrealineado o declarado con un especificador de alineación que especifique una alineación extendida, S tiene una alineación fundamental.

La versión atómica de cada tipo aritmético o puntero tiene una alineación fundamental.

(desde C11)

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a estándares C publicados anteriormente.

DR Aplicado a Comportamiento publicado Comportamiento correcto
DR 445 C11 un tipo podría tener alineación extendida sin involucrar _Alignas debe tener alineación fundamental

Referencias

  • Estándar C17 (ISO/IEC 9899:2018):
  • 3.15 objeto (p: 5)
  • 6.2.6 Representaciones de tipos (p: 33-35)
  • 6.2.8 Alineación de objetos (p: 36-37)
  • 6.5/6-7 Expresiones (p: 55-56)
  • Estándar C11 (ISO/IEC 9899:2011):
  • 3.15 object (p: 6)
  • 6.2.6 Representations of types (p: 44-46)
  • 6.2.8 Alignment of objects (p: 48-49)
  • 6.5/6-7 Expressions (p: 77)
  • Estándar C99 (ISO/IEC 9899:1999):
  • 3.2 alineación (p: 3)
  • 3.14 objeto (p: 5)
  • 6.2.6 Representaciones de tipos (p: 37-39)
  • 6.5/6-7 Expresiones (p: 67-68)
  • Estándar C89/C90 (ISO/IEC 9899:1990):
  • 1.6 Definiciones de términos

Véase también