¿Qué tipo de tipo de datos C11 es una matriz según AMD64 ABI?
assembly types (1)
Estaba investigando la convención de llamadas de x86_64 que se usa en OSX y estaba leyendo la sección llamada "Agregados y Uniones" en el estándar System V x86-64 ABI ). Menciona las matrices y me di cuenta de que era como una matriz c de longitud fija, por ejemplo, int[5]
.
uint8_t[3]
a "3.2.3 Parameter Passing" para leer sobre cómo se pasaron las matrices y si estoy entendiendo correctamente, algo como uint8_t[3]
debe pasar en los registros ya que es más pequeño que el límite de cuatro bytes impuesto por la regla 1 de la clasificación de los tipos de agregado (página 18 cerca de la parte inferior).
Después de compilar, veo que, en cambio, se pasa como un puntero. (Estoy compilando con clang-703.0.31 de Xcode 7.3.1 en OSX 10.11.6).
La fuente de ejemplo que estaba usando para compilar es la siguiente:
#include <stdio.h>
#define type char
extern void doit(const type[3]);
extern void doitt(const type[5]);
extern void doittt(const type[16]);
extern void doitttt(const type[32]);
extern void doittttt(const type[40]);
int main(int argc, const char *argv[]) {
const char a[3] = { 1, 2, 3 };
const char b[5] = { 1, 2, 3, 4, 5 };
const char c[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1 };
const char d[32] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1 };
const char e[40] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
doit(a);
doitt(b);
doittt(c);
doitttt(d);
doittttt(e);
}
Lo descargo en un archivo llamado ac
y uso el siguiente comando para compilar: clang -c ac -o ao
. Yo uso otool para analizar el conjunto generado (ejecutando otool -tV ao
) y obtengo el siguiente resultado:
a.o:
(__TEXT,__text) section
_main:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp, %rbp
0000000000000004 subq $0x10, %rsp
0000000000000008 leaq _main.a(%rip), %rax
000000000000000f movl %edi, -0x4(%rbp)
0000000000000012 movq %rsi, -0x10(%rbp)
0000000000000016 movq %rax, %rdi
0000000000000019 callq _doit
000000000000001e leaq _main.b(%rip), %rdi
0000000000000025 callq _doitt
000000000000002a leaq _main.c(%rip), %rdi
0000000000000031 callq _doittt
0000000000000036 leaq _main.d(%rip), %rdi
000000000000003d callq _doitttt
0000000000000042 leaq _main.e(%rip), %rdi
0000000000000049 callq _doittttt
000000000000004e xorl %eax, %eax
0000000000000050 addq $0x10, %rsp
0000000000000054 popq %rbp
0000000000000055 retq
O de manera equivalente, aquí está en el explorador del compilador Godbolt con clang3.7 , que apunta a Linux que usa el mismo ABI.
Entonces, me preguntaba si alguien podría llevarme a qué tipos de datos en C11 se aplican a las matrices. (Parece que clang utiliza el C11 como opción predeterminada; consulte la propaganda aquí debajo de la función en línea C99).
También hice una investigación similar con ARM y encontré resultados similares, aunque el estándar ARM también especifica que existe un tipo agregado de matriz .
Además, ¿existe alguna norma que especifique que una matriz de longitud fija debe tratarse como un puntero?
Los arrays desnudos como args de funciones en C y C ++ siempre se degradan en punteros, al igual que en muchos otros contextos.
Las matrices dentro de struct
s o union
s no se transfieren por valor. Esta es la razón por la cual los ABI deben preocuparse por cómo se pasan, incluso aunque no ocurra en C para arreglos desnudos.
Como señala Keith Thomson , la parte relevante del estándar C es N1570, sección 6.7.6.3, párrafo 7
Una declaración de un parámetro como "matriz de tipo" se ajustará a "indicador calificado para escribir", donde los calificadores de tipo (si los hay) son los especificados dentro de [y] de la derivación de tipo de matriz ... (cosas sobre
foo[static 10]
, ver abajo)
Tenga en cuenta que las matrices multidimensionales funcionan como matrices de tipo array, por lo que solo el nivel más externo de "array-ness" se convierte en un puntero al tipo de matriz.
Terminología: el documento x86-64 ABI utiliza la misma terminología que ARM, donde struct
y arrays son "agregados" (elementos múltiples en direcciones secuenciales). Entonces la frase "agregados y uniones" surge mucho, porque los union
se manejan de manera similar por el lenguaje y el ABI.
Es la regla recursiva para manejar los tipos compuestos (struct / union / class) que pone en juego las reglas de paso de matriz en el ABI. Esta es la única forma en que verá asm que copia una matriz en la pila como parte de una función arg, para C o C ++
struct s { int a[8]; };
void ext(struct s byval);
void foo() { struct s tmp = {{0}}; ext(tmp); }
gcc6.1 lo compila (para AMD64 SysV ABI, con -O3
) para lo siguiente:
sub rsp, 40 # align the stack and leave room for `tmp` even though it''s never stored?
push 0
push 0
push 0
push 0
call ext
add rsp, 72
ret
En el ABI x86-64, el valor de paso pasa por copia real (en registros o la pila), no por punteros ocultos.
Tenga en cuenta que return-by-value pasa un puntero como primer arg "oculto" (en rdi
), cuando el valor de retorno es demasiado grande para caber en la concatenación de 128 bits de rdx:rax
(y no es un vector que se devuelve en Reglas vectoriales, etc. etc.)
El ABI podría usar un puntero oculto para objetos de valor por encima de cierto tamaño y confiar en que la función llamada no modificará el original, pero eso no es lo que el x86-64 ABI elige hacer. Eso sería mejor en algunos casos (especialmente para C ++ ineficiente con mucha copia sin modificación (es decir, desperdiciado)), pero peor en otros casos.
Lectura de bonificación de SysV ABI : como señala la wiki de la etiqueta x86 , la versión actual de la norma ABI no documenta completamente el comportamiento en el que confían los compiladores: clang / gcc sign / zero extienden args estrechos a 32 bits .
Tenga en cuenta que para garantizar realmente que una función arg es una matriz de tamaño fijo, C99 y posterior le permite usar la palabra clave static
de una nueva manera : en tamaños de matriz. (Todavía se pasa como un puntero, por supuesto. Esto no cambia el ABI).
void bar(int arr[static 10]);
Esto permite que sizeof(arr)
funcione como cabría esperar dentro de la función llamada, y permite advertencias al compilador sobre salirse de los límites. También potencialmente permite una mejor optimización si el compilador sabe que tiene acceso a los elementos que el origen C no tiene. (Ver esta publicación en el blog ).
La misma página de palabras clave para C ++ indica que ISO C ++ no admite este uso de static
; es otra de esas características exclusivas de C, junto con las matrices de longitud variable C99 y algunas otras ventajas que C ++ no tiene.
En C ++, puede usar std::array<int,10>
para obtener información de tamaño de tiempo de compilación pasada a la persona que llama. Sin embargo, debe pasarlo manualmente por referencia si eso es lo que desea, ya que es, por supuesto, solo una clase que contiene un int arr[10]
. A diferencia de una matriz estilo C, no decae a T*
automáticamente.
El documento ARM que vinculó no parece realmente llamar a las matrices un tipo agregado: la Sección 4.3 Tipos compuestos (que discute la alineación) distingue las matrices de los tipos agregados, aunque parecen ser un caso especial de su definición para los agregados.
Un tipo compuesto es una colección de uno o más tipos de datos fundamentales que se manejan como una sola entidad en el nivel de llamada de procedimiento. Un tipo compuesto puede ser cualquiera de:
- Un agregado, donde los miembros se disponen secuencialmente en la memoria
- Una unión, donde cada uno de los miembros tiene la misma dirección
- Una matriz, que es una secuencia repetida de algún otro tipo (su tipo base).
Las definiciones son recursivas; es decir, cada uno de los tipos puede contener un tipo compuesto como miembro
"Compuesto" es un término general que incluye matrices, estructuras y uniones.