Objects and alignment
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
-
tamaño (se puede determinar con
sizeof) -
requisito de alineación
(se puede determinar mediante
_Alignof(hasta C23)alignof(desde C23) ) (desde C11) - duración de almacenamiento (automática, estática, asignada, local al hilo)
- tiempo de vida (igual a la duración de almacenamiento o temporal)
- tipo efectivo (ver abajo)
- valor (que puede ser indeterminado)
- opcionalmente, un identificador que denota este objeto.
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
|
(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
Si un tipo struct o union
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
|
Documentación de C++
para
Object
|