Partilhar via


Visão geral das convenções x64 ABI

Este tópico descreve a interface binária de aplicativo (ABI) básica para x64, a extensão de 64 bits para a arquitetura x86. Ele abrange tópicos como a convenção de chamada, layout de tipo, uso de pilha e registro e muito mais.

Convenções de chamada x64

Duas diferenças importantes entre x86 e x64 são:

  • Capacidade de endereçamento de 64 bits
  • Dezasseis registos de 64 bits para uso geral.

Dado o conjunto de registros expandido, o x64 usa a __fastcall convenção de chamada e um modelo de tratamento de exceções baseado em RISC.

A __fastcall convenção usa registros para os quatro primeiros argumentos e o quadro de pilha para passar mais argumentos. Para obter detalhes sobre a convenção de chamada x64, incluindo uso de registro, parâmetros de pilha, valores de retorno e desenrolamento de pilha, consulte Convenção de chamada x64.

Para obter mais informações sobre a __vectorcall convenção de chamada, consulte __vectorcall.

Ativar otimização do compilador x64

A seguinte opção de compilador ajuda você a otimizar seu aplicativo para x64:

Tipo x64 e layout de armazenamento

Esta seção descreve o armazenamento de tipos de dados para a arquitetura x64.

Tipos escalares

Embora seja possível acessar dados com qualquer alinhamento, alinhe os dados em seu limite natural, ou um múltiplo de seu limite natural, para evitar perda de desempenho. Enums são inteiros constantes e são tratados como inteiros de 32 bits. A tabela a seguir descreve a definição de tipo e o armazenamento recomendado para dados no que diz respeito ao alinhamento usando os seguintes valores de alinhamento:

  • Byte - 8 bits
  • Word - 16 bits
  • Palavra dupla - 32 bits
  • Quadword - 64 bits
  • Octaword - 128 bits
Tipo escalar Tipo de dados C Tamanho de armazenamento (em bytes) Alinhamento recomendado
INT8 char 1 byte
UINT8 unsigned char 1 byte
INT16 short 2 Palavra
UINT16 unsigned short 2 Palavra
INT32 int, long 4 Palavra dupla
UINT32 unsigned int, unsigned long 4 Palavra dupla
INT64 __int64 8 Quadword
UINT64 unsigned __int64 8 Quadword
FP32 (precisão única) float 4 Palavra dupla
FP64 (precisão dupla) double 8 Quadword
POINTER * 8 Quadword
__m64 struct __m64 8 Quadword
__m128 struct __m128 16 Octapalavra

Layout de agregado e união x64

Outros tipos, como arrays, estruturas e uniões, têm requisitos de alinhamento mais rígidos que garantem armazenamento e recuperação de dados consistentes. Aqui estão as definições para matriz, estrutura e união:

  • Matriz

    Contém um grupo ordenado de objetos de dados adjacentes. Cada objeto é chamado de elemento. Todos os elementos dentro de uma matriz têm o mesmo tamanho e tipo de dados.

  • Estrutura

    Contém um grupo ordenado de objetos de dados. Ao contrário dos elementos de uma matriz, os membros de uma estrutura podem ter diferentes tipos e tamanhos de dados.

  • União

    Um objeto que contém qualquer um de um conjunto de membros nomeados. Os membros do conjunto nomeado podem ser de qualquer tipo. O armazenamento alocado para uma união é igual ao armazenamento necessário para o maior membro dessa união, mais qualquer preenchimento necessário para o alinhamento.

A tabela a seguir mostra o alinhamento fortemente recomendado para os membros escalares de sindicatos e estruturas.

Tipo escalar Tipo de dados C Alinhamento necessário
INT8 char byte
UINT8 unsigned char byte
INT16 short Palavra
UINT16 unsigned short Palavra
INT32 int, long Palavra dupla
UINT32 unsigned int, unsigned long Palavra dupla
INT64 __int64 Quadword
UINT64 unsigned __int64 Quadword
FP32 (precisão única) float Palavra dupla
FP64 (precisão dupla) double Quadword
POINTER * Quadword
__m64 struct __m64 Quadword
__m128 struct __m128 Octapalavra

Aplicam-se as seguintes regras de alinhamento agregado:

  • O alinhamento de uma matriz é o mesmo que o alinhamento de um dos elementos da matriz.

  • O alinhamento do início de uma estrutura ou de um sindicato é o alinhamento máximo de qualquer membro individual. Cada membro dentro da estrutura ou união deve ser posicionado no alinhamento correto, conforme definido na tabela anterior, o que pode exigir almofadamento interno implícito, o que pode depender do membro anterior.

  • O tamanho da estrutura deve ser um múltiplo integral de seu alinhamento, o que pode exigir preenchimento após o último membro. Como as estruturas e uniões podem ser agrupadas em matrizes, cada elemento de matriz de uma estrutura ou união deve começar e terminar no alinhamento adequado previamente determinado.

  • É possível alinhar os dados de forma a ser maior do que os requisitos de alinhamento, desde que as regras anteriores sejam mantidas.

  • Um compilador individual pode ajustar a embalagem de uma estrutura por razões de tamanho. Por exemplo, /Zp (Struct Member Alignment) permite ajustar a embalagem de estruturas.

Exemplos de alinhamento de estrutura x64

Os quatro exemplos a seguir declaram uma estrutura alinhada ou união, e as figuras correspondentes ilustram o layout dessa estrutura ou união na memória. Cada coluna em uma figura representa um byte de memória, e o número na coluna indica o deslocamento desse byte. O nome na segunda linha de cada figura corresponde ao nome de uma variável na declaração. As colunas sombreadas indicam o preenchimento necessário para alcançar o alinhamento especificado.

Exemplo 1

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

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

Diagrama mostrando o layout da estrutura para o exemplo 1. O diagrama mostra 2 bytes de memória. O membro a, um short, ocupa os bytes de 0 até 1.

Exemplo 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 mostrando o layout da estrutura, por exemplo 2.

O diagrama mostra 24 bytes de memória. O membro a, um int, ocupa bytes de 0 a 3. O diagrama mostra preenchimento para os bytes 4 a 7. O membro b, do tipo double, ocupa bytes de 8 a 15. Membro c, um tipo short, ocupa os bytes 16 e 17. Os bytes 18 a 23 não são utilizados.

Exemplo 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 mostrando o layout da estrutura, por exemplo, 3.

O diagrama mostra 12 bytes de memória. Membro a, um char, ocupa byte 0. O byte 1 é utilizado como preenchimento. O membro b, um curto, ocupa bytes de 2 a 4. O membro c, um caractere, ocupa o byte 4. Os bytes 5 a 7 são espaços de enchimento. Membro d, um int, ocupa os bytes de 8 a 11.

Exemplo 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 mostrando o layout da união, por exemplo 4.

O diagrama mostra 8 bytes de memória. O membro p, um caractere, ocupa o byte 0. Membro s, um curto, ocupa bytes de 0 a 1. O membro l, do tipo longo, ocupa os bytes de 0 a 3. Os bytes 4 a 7 são usados como preenchimento.

Campos de bits

Os campos de bits de estrutura são limitados a 64 bits e podem ser do tipo signed int, unsigned int, int64 ou unsigned int64. Os campos de bits que cruzam o limite de tipo ignorarão bits para alinhar o campo de bits ao próximo alinhamento de tipo. Por exemplo, campos de bits inteiros podem não cruzar um limite de 32 bits.

Conflitos com o compilador x86

Os tipos de dados maiores que 4 bytes não são alinhados automaticamente na pilha quando você usa o compilador x86 para compilar um aplicativo. Como a arquitetura do compilador x86 é uma pilha alinhada de 4 bytes, qualquer coisa maior que 4 bytes, por exemplo, um inteiro de 64 bits, não pode ser alinhada automaticamente a um endereço de 8 bytes.

Trabalhar com dados não alinhados tem duas implicações.

  • Pode levar mais tempo para acessar locais não alinhados do que para acessar locais alinhados.

  • Locais não alinhados não podem ser usados em operações interligadas.

Se você precisar de um alinhamento mais rigoroso, use __declspec(align(N)) em suas declarações de variáveis. Isso faz com que o compilador alinhe dinamicamente a pilha para atender às suas especificações. No entanto, ajustar dinamicamente a pilha em tempo de execução pode causar uma execução mais lenta do seu aplicativo.

uso de registos x64

A arquitetura x64 fornece 16 registradores de uso geral (doravante referidos como registros inteiros), bem como 16 registradores XMM/YMM disponíveis para uso de ponto flutuante. Os registos voláteis são registos de rascunho presumidos pelo chamador como destruídos através de uma chamada. Os registradores não voláteis são obrigados a reter seus valores em uma chamada de função e devem ser salvos pelo destinatário, se usados.

Registrar volatilidade e preservação

A tabela a seguir descreve como cada registro é usado em chamadas de função:

Registar-se Situação Utilização
RAX Volátil Registo de valor de retorno
RCX Volátil Primeiro argumento inteiro
RDX Volátil Segundo argumento inteiro
R8 Volátil Terceiro argumento inteiro
R9 Volátil Quarto argumento inteiro
R10:R11 Volátil Deve ser preservado conforme a necessidade do chamador; usado em instruções syscall/sysret
R12:R15 Não volátil Deve ser preservado pelo destinatário
IDI Não volátil Deve ser preservado pelo destinatário
LER Não volátil Deve ser preservado pelo destinatário
RBX Não volátil Deve ser preservado pelo destinatário
RBP Não volátil Pode ser usado como um ponteiro de quadro; deve ser preservado pelo destinatário
DERP Não volátil Ponteiro de pilha
XMM0, YMM0 Volátil Primeiro argumento das PF; primeiro argumento de tipo vetorial quando __vectorcall é usado
XMM1, YMM1 Volátil Segundo argumento de ponto flutuante; segundo argumento de tipo vetorial quando __vectorcall é usado
XMM2, YMM2 Volátil Terceiro argumento das PF; terceiro argumento de tipo vetorial quando __vectorcall é usado
XMM3, YMM3 Volátil Quarto argumento das PF; quarto argumento de tipo vetorial quando __vectorcall usado
XMM4, YMM4 Volátil Deve ser preservado conforme a necessidade do chamador; quinto argumento do tipo vetor quando __vectorcall é usado
XMM5, YMM5 Volátil Deve ser preservado conforme a necessidade do chamador; sexto argumento de tipo vetorial quando __vectorcall usado
XMM6: XMM15, YMM6: YMM15 Não volátil (XMM), volátil (metade superior do YMM) Deve ser preservado pelo destinatário. Os registros YMM devem ser preservados conforme a necessidade do chamador.

Na saída da função e na entrada da função para chamadas da Biblioteca de Tempo de Execução C e chamadas do sistema Windows, espera-se que o sinalizador de direção no registro de sinalizadores da CPU seja limpo.

Uso da pilha

Para obter detalhes sobre alocação de pilha, alinhamento, tipos de função e quadros de pilha em x64, consulte Uso de pilha x64.

Prolog e epílogo

Cada função que aloca espaço na pilha, chama outras funções, salva registos não voláteis ou utiliza manipulação de exceções deve ter um prólogo cujos limites de endereço são descritos nos dados de desenrolar associados à respetiva entrada da tabela de funções, e epílogos em cada saída de uma função. Para obter detalhes sobre o prolog e o código epilog necessários em x64, consulte x64 prolog e epilog.

Tratamento de exceções x64

Para obter informações sobre as convenções e estruturas de dados usadas para implementar o tratamento de exceções estruturadas e o comportamento de tratamento de exceções C++ no x64, consulte Tratamento de exceções x64.

Intrínsecos e montagem em linha

Uma das restrições para o compilador x64 é não haver suporte a assembler em linha. Isso significa que as funções que não podem ser escritas em C ou C++ terão que ser escritas como sub-rotinas ou como funções intrínsecas suportadas pelo compilador. Algumas funções são sensíveis ao desempenho, enquanto outras não. As funções sensíveis ao desempenho devem ser implementadas como funções intrínsecas.

Os intrínsecos suportados pelo compilador são descritos em Intrínsecos do compilador.

Formato de imagem x64

O formato de imagem executável x64 é PE32+. As imagens executáveis (DLLs e EXEs) são restritas a um tamanho máximo de 2 gigabytes, portanto, o endereçamento relativo com um deslocamento de 32 bits pode ser usado para endereçar dados de imagem estática. Esses dados incluem a tabela de endereços de importação, constantes de cadeia de caracteres, dados globais estáticos e assim por diante.

Ver também

Convenções de Chamada