Compartir a través de


Información general sobre las convenciones ABI de x64

En este tema se describe la interfaz binaria de aplicación (ABI) básica para x64, la extensión de 64 bits a la arquitectura x86. Trata temas como la convención de llamada, el diseño de tipos, la pila y el uso del registro, etc.

Convenciones de llamada x64

Dos diferencias importantes entre x86 y x64 son:

  • Capacidad de direccionamiento de 64 bits
  • Dieciséis registros de 64 bits para uso general.

Dado el conjunto de registros expandido, x64 usa la convención de llamada __fastcall y un modelo de control de excepciones basado en RISC.

La convención __fastcall usa registros para los cuatro primeros argumentos, y el marco de pila para pasar más argumentos. Para obtener información detallada sobre la convención de llamada de x64, incluido el uso de registros, parámetros de la pila, valores devueltos y el desenredado de la pila, vea Convención de llamada de x64.

Para más información sobre la convención de llamada de__vectorcall, consulte __vectorcall.

Habilitación de la optimización del compilador x64

La siguiente opción del compilador le ayuda a optimizar la aplicación para x64:

Tipo x64 y diseño de almacenamiento

En esta sección se describe el almacenamiento de tipos de datos para la arquitectura x64.

Tipos escalares

Aunque es posible acceder a datos con cualquier alineación, alinee los datos por su límite natural, o un múltiplo de su límite natural, para evitar un deterioro del rendimiento. Las enumeraciones son enteros constantes y se tratan como enteros de 32 bits. En la tabla siguiente se describe la definición de tipo y el almacenamiento recomendado para los datos con relación a la alineación mediante los valores de alineación siguientes:

  • Byte: 8 bits
  • Palabra: 16 bits
  • Doble palabra: 32 bits
  • Palabra cuádruple: 64 bits
  • Doble palabra cuádruple: 128 bits
Tipo escalar tipo de datos C Tamaño de almacenamiento (en bytes) Alineación recomendada
INT8 char 1 Byte
UINT8 unsigned char 1 Byte
INT16 short 2 Word
UINT16 unsigned short 2 Word
INT32 int, long 4 Doble palabra
UINT32 unsigned int, unsigned long 4 Doble palabra
INT64 __int64 8 Palabra cuádruple
UINT64 unsigned __int64 8 Palabra cuádruple
FP32 (precisión sencilla) float 4 Doble palabra
FP64 (precisión doble) double 8 Palabra cuádruple
POINTER * 8 Palabra cuádruple
__m64 struct __m64 8 Palabra cuádruple
__m128 struct __m128 16 Doble palabra cuádruple

Diseño de agregado y unión x64

Otros tipos, como las matrices, estructuras y uniones, tienen requisitos de alineación más estrictos que garantizan el almacenamiento y la recuperación de datos coherentes de los agregados y las uniones. Estas son las definiciones de matriz, estructura y unión:

  • Matriz

    Contiene un grupo ordenado de objetos de datos adyacentes. Cada objeto se denomina elemento. Todos los elementos de una matriz tienen el mismo tamaño y tipo de datos.

  • Estructura

    Contiene un grupo ordenado de objetos de datos. A diferencia de los elementos de una matriz, los miembros de una estructura pueden tener distintos tamaños y tipos de datos.

  • Unión

    Objeto que contiene cualquiera de un conjunto de miembros con nombre. Los miembros del conjunto con nombre pueden ser de cualquier tipo. El almacenamiento asignado para una unión es igual al almacenamiento necesario para el miembro más grande de esa unión, además de cualquier relleno necesario para la alineación.

En la tabla siguiente se muestra la alineación recomendada para los miembros escalares de uniones y estructuras.

Tipo escalar Tipo de datos de C Alineación necesaria
INT8 char Byte
UINT8 unsigned char Byte
INT16 short Word
UINT16 unsigned short Word
INT32 int, long Doble palabra
UINT32 unsigned int, unsigned long Doble palabra
INT64 __int64 Palabra cuádruple
UINT64 unsigned __int64 Palabra cuádruple
FP32 (precisión sencilla) float Doble palabra
FP64 (precisión doble) double Palabra cuádruple
POINTER * Palabra cuádruple
__m64 struct __m64 Palabra cuádruple
__m128 struct __m128 Doble palabra cuádruple

Se aplican las siguientes reglas de alineación de agregados:

  • La alineación de una matriz es la misma que la alineación de uno de los elementos de la matriz.

  • La alineación del inicio de una estructura o una unión es la alineación máxima de cualquier miembro individual. Cada miembro de la estructura o unión se debe colocar en su alineación adecuada, como se ha definido en la tabla anterior, lo que puede requerir un relleno interno implícito, en función del miembro anterior.

  • El tamaño de la estructura debe ser un múltiplo entero de su alineación, lo que puede requerir relleno después del último miembro. Como las estructuras y las uniones se pueden agrupar en matrices, cada elemento de matriz de una estructura o unión debe comenzar y finalizar en la alineación adecuada determinada antes.

  • Es posible alinear datos de tal forma que sean mayores que los requisitos de alineación, siempre que se mantengan las reglas anteriores.

  • Un compilador individual puede ajustar el empaquetado de una estructura por motivos de tamaño. Por ejemplo, /Zp (Alineación de miembros de estructura) permite ajustar el empaquetado de estructuras.

Ejemplos de alineación de estructuras x64

En los cuatro ejemplos siguientes se declara una estructura alineada o una unión, y en las figuras correspondientes se muestra el diseño de esa estructura o unión en la memoria. Cada columna de una ilustración representa un byte de memoria, y el número de la columna indica el desplazamiento de ese byte. El nombre de la segunda fila de cada ilustración se corresponde con el nombre de una variable en la declaración. Las columnas sombreadas indican el relleno necesario para lograr la alineación especificada.

Ejemplo 1

// Total size = 2 bytes, alignment = 2 bytes (word).

_declspec(align(2)) struct {
    short a;      // +0; size = 2 bytes
}

Diagrama en el que se muestra el diseño de estructura del ejemplo 1.

Ejemplo 2

// Total size = 24 bytes, alignment = 8 bytes (quadword).

_declspec(align(8)) struct {
    int a;       // +0; size = 4 bytes
    double b;    // +8; size = 8 bytes
    short c;     // +16; size = 2 bytes
}

Diagrama en el que se muestra el diseño de estructura del ejemplo 2.

Ejemplo 3

// Total size = 12 bytes, alignment = 4 bytes (doubleword).

_declspec(align(4)) struct {
    char a;       // +0; size = 1 byte
    short b;      // +2; size = 2 bytes
    char c;       // +4; size = 1 byte
    int d;        // +8; size = 4 bytes
}

Diagrama en el que se muestra el diseño de estructura del ejemplo 3.

Ejemplo 4

// Total size = 8 bytes, alignment = 8 bytes (quadword).

_declspec(align(8)) union {
    char *p;      // +0; size = 8 bytes
    short s;      // +0; size = 2 bytes
    long l;       // +0; size = 4 bytes
}

Diagrama en el que se muestra el diseño de unión del ejemplo 4.

Campos de bits

Los campos de bits de estructura se limitan a 64 bits y pueden ser de tipo signed int, unsigned int, int64 o unsigned int64. Los campos de bits que cruzan el límite de tipos omiten bits para alinear el campo de bits con la alineación de tipos siguiente. Por ejemplo, los campos de bits enteros no pueden cruzar un límite de 32 bits.

Conflictos con el compilador de x86

Los tipos de datos de más de 4 bytes no se alinean automáticamente en la pila cuando se usa el compilador x86 para compilar una aplicación. Como la arquitectura del compilador x86 es una pila alineada de 4 bytes, todo lo que sea mayor de 4 bytes, por ejemplo, un entero de 64 bits, no se puede alinear de forma automática con una dirección de 8 bytes.

Trabajar con datos sin alinear tiene dos implicaciones.

  • Se puede tardar más en acceder a ubicaciones no alineadas de lo que se tarda en acceder a ubicaciones alineadas.

  • Las ubicaciones no alineadas no se pueden usar en operaciones de bloqueo.

Si necesita una alineación más estricta, use __declspec(align(N)) en las declaraciones de variables. Esto hace que el compilador alinee de forma dinámica la pila para cumplir con las especificaciones. Pero el ajuste dinámico de la pila en tiempo de ejecución puede ralentizar la ejecución de la aplicación.

Uso de registros x64

La arquitectura x64 proporciona 16 registros generales (que se denominarán registros enteros a partir de ahora), así como otros 16 registros de XMM/YMM, disponibles para el uso de puntos flotantes. Los registros volátiles son registros residuales que el llamador da por hecho que van a destruirse a lo largo de la llamada. En cuanto a los registros no volátiles, es necesarios conservar sus valores a lo largo de la llamada de función, y el destinatario de la llamada debe guardarlos si se usan.

Volatilidad y conservación de los registros

En la siguiente tabla se explica el uso de cada registro en las llamadas de función:

Registro Estado Usar
RAX Volátil Registro de valor devuelto
RCX Volátil Primer argumento de entero
RDX Volátil Segundo argumento de entero
R8 Volátil Tercer argumento de entero
R9 Volátil Cuarto argumento de entero
R10:R11 Volátil El llamador debe conservarlo según sea necesario; utilizado en instrucciones syscall/sysret
R12:R15 No volátil El destinatario de la llamada debe conservarlo
RDI No volátil El destinatario de la llamada debe conservarlo
RSI No volátil El destinatario de la llamada debe conservarlo
RBX No volátil El destinatario de la llamada debe conservarlo
RBP No volátil Se puede usar como un puntero de marco; el destinatario de la llamada debe conservarlo según sea necesario
RSP No volátil Puntero de pila
XMM0, YMM0 Volátil Primer argumento de FP; primer argumento de tipo vectorial cuando se usa __vectorcall
XMM1, YMM1 Volátil Segundo argumento de FP; segundo argumento de tipo vectorial cuando se usa __vectorcall
XMM2, YMM2 Volátil Tercer argumento de FP; tercer argumento de tipo vectorial cuando se usa __vectorcall
XMM3, YMM3 Volátil Cuarto argumento de FP; cuarto argumento de tipo vectorial cuando se usa __vectorcall
XMM4, YMM4 Volátil El llamador debe conservarlo según sea necesario; quinto argumento de tipo vectorial cuando se usa __vectorcall
XMM5, YMM5 Volátil El llamador debe conservarlo según sea necesario; sexto argumento de tipo vectorial cuando se usa __vectorcall
XMM6:XMM15, YMM6:YMM15 No volátil (XMM), volátil (mitad superior de YMM) Lo debe conservar el destinatario. El llamador debe conservar los registros de YMM según sea necesario.

En la salida y entrada de la función para las llamadas a la biblioteca en tiempo de ejecución de C y las llamadas del sistema de Windows, se espera que la marca de dirección del registro de marcas de CPU se borre.

Uso de la pila

Para obtener más información sobre la asignación de pila, la alineación, los tipos de función y los marcos de pila en x64, vea Uso de la pila de x64.

Prólogo y epílogo

Todas las funciones que asignan espacio de pila, llaman a otras funciones, guardan registros no volátiles o usan el control de excepciones deben tener un prólogo cuyos límites de dirección se describen en los datos de desenredado asociados a la entrada de la tabla de funciones correspondiente, así como epílogos en cada salida de una función. Para obtener más información sobre el código de prólogo y epílogo necesario en x64, vea Prólogo y epílogo de x64.

Control de excepciones x64

Para obtener información sobre las convenciones y estructuras de datos que se usan para implementar el control de excepciones estructurado y el comportamiento del control de excepciones de C++ en x64, vea Control de excepciones en x64.

Elementos intrínsecos y ensamblado en línea

Una de las restricciones del compilador x64 es la falta de compatibilidad con el ensamblador en línea. Esto significa que las funciones que no se pueden escribir en C o C++ se tienen que escribir como subrutinas o como funciones intrínsecas admitidas por el compilador. Algunas funciones son sensibles el rendimiento y otras no. Las funciones sensibles al rendimiento se deben implementar como funciones intrínsecas.

Las funciones intrínsecas que admite el compilador se describen en Intrínsecos del controlador.

Formato de imagen x64

El formato de imagen ejecutable de x64 es PE32+. Las imágenes ejecutables (tanto DLL como EXE) están restringidas a un tamaño máximo de 2 gigabytes, por lo que se puede usar el direccionamiento relativo con un desplazamiento de 32 bits para tratar los datos de imagen estáticos. Estos datos incluyen la tabla de direcciones de importación, las constantes de cadena y los datos globales estáticos, entre otros.

Consulte también

Convenciones de llamada