Partilhar via


Visão geral das convenções ABI x64

Este tópico descreve a ABI (interface binária de aplicativo) básica para x64, a extensão de 64 bits para a arquitetura x86. Ele aborda 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:

  • Funcionalidade de endereçamento de 64 bits
  • Dezesseis registros de 64 bits para uso geral.

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

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

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

Habilitar a otimização do compilador x64

A seguinte opção do 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 no limite natural deles ou um múltiplo do limite natural deles, para evitar perda de desempenho. Enumerações 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, pois se refere ao alinhamento usando os seguintes valores de alinhamento:

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

Layout de união e agregação x64

Outros tipos, como matrizes, structs e uniões, têm requisitos de alinhamento mais rigorosos que garantem a agregação consistente e a recuperação de dados e armazenamento de união. Aqui estão as definições para matriz, estrutura e união:

  • Array

    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 tipos e tamanhos de dados diferentes.

  • Union

    Um objeto que contém qualquer um dos membros nomeados de um conjunto. 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, além de qualquer preenchimento necessário para alinhamento.

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

Tipo escalar Tipo de dados em C++ Alinhamento Necessário
INT8 char Byte
UINT8 unsigned char Byte
INT16 short Word
UINT16 unsigned short Word
INT32 int, long Doubleword
UINT32 unsigned int, unsigned long Doubleword
INT64 __int64 Quadword
UINT64 unsigned __int64 Quadword
FP32 (precisão simples) float Doubleword
FP64 (precisão dupla) double Quadword
POINTER * Quadword
__m64 struct __m64 Quadword
__m128 struct __m128 Octaword

As seguintes regras de alinhamento agregado se aplicam:

  • 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 uma união é o alinhamento máximo de qualquer membro individual. Cada membro dentro da estrutura ou união precisa ser colocado no alinhamento adequado, conforme definido na tabela anterior, o que pode exigir preenchimento interno implícito, dependendo do membro anterior.

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

  • É possível alinhar dados de maneira a exceder os requisitos de alinhamento, desde que as regras anteriores sejam mantidas.

  • Um compilador individual pode ajustar o empacotamento de uma estrutura por motivos de tamanho. Por exemplo, /Zp (Alinhamento de membro struct) permite ajustar o empacotamento de estruturas.

Exemplos de alinhamento de estrutura x64

Os quatro exemplos a seguir declaram uma estrutura ou união alinhada, 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 do exemplo 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 do exemplo 2.

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 do exemplo 3.

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 exemplo de layout de 4 uniões.

Campos de bits

Os campos de bits de estrutura são limitados a 64 bits e podem ser do tipo int, int sem sinal, int64 ou int64 sem sinal. 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, os campos de bits de inteiro 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 usando dados não atribuídos tem duas implicações.

  • Pode levar mais tempo para acessar locais não atribuídos do que é necessário para acessar locais alinhados.

  • Locais não atribuídos não podem ser usados em operações interligadas.

Se você precisar de um alinhamento mais estrito, use __declspec(align(N)) em suas declarações de variável. 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 aplicativo.

Uso do registro x64

A arquitetura x64 sustenta 16 registros de uso geral (de agora em diante, chamados de registros de inteiros), bem como para 16 registros XMM/YMM disponíveis para uso de ponto flutuante. Os registros voláteis são registros a partir do zero presumidos pelo chamador para serem destruídos em uma chamada. Os registros não voláteis são obrigados a manter seus valores em uma chamada de função e devem ser salvos pelo receptor da chamada se usados.

Registrar volatilidade e preservação

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

Registrar-se Status Usar
RAX Volátil Registro de valores retornados
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 receptor da chamada
RDI Não volátil Deve ser preservado pelo receptor da chamada
RSI Não volátil Deve ser preservado pelo receptor da chamada
RBX Não volátil Deve ser preservado pelo receptor da chamada
RBP Não volátil Pode ser usado como um ponteiro de quadro; deve ser preservado pelo receptor da chamada
RSP Não volátil Ponteiro de pilha
XMM0, YMM0 Volátil Primeiro argumento FP; primeiro argumento de tipo vetorial quando __vectorcall for usado
XMM1, YMM1 Volátil Segundo argumento FP; segundo argumento de tipo vetorial quando __vectorcall for usado
XMM2, YMM2 Volátil Terceiro argumento FP; terceiro argumento de tipo vetorial quando __vectorcall for usado
XMM3, YMM3 Volátil Quarto argumento FP; quarto argumento de tipo vetorial quando __vectorcall for usado
XMM4, YMM4 Volátil Deve ser preservado conforme necessário pelo chamador; quinto argumento de tipo vetorial quando __vectorcall for usado
XMM5, YMM5 Volátil Deve ser preservado conforme necessário pelo chamador; sexto argumento de tipo vetorial quando __vectorcall for usado
XMM6:XMM15, YMM6:YMM15 Não volátil (XMM), Volátil (metade superior de YMM) Precisa ser preservado pelo computador chamado. Os registros YMM devem ser preservados conforme necessário pelo chamador.

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

Uso da pilha

Para obter detalhes sobre alocação de pilha, alinhamento, tipos de função e registros de ativação no x64, confira Uso da pilha x64.

Prólogo e epílogo

Cada função que aloca espaço de pilha, chama outras funções, salva registros não voláteis ou usa tratamento de exceção precisa ter uma caixa de diálogo cujos limites de endereço são descritos nos dados de desenrolamento associados à respectiva entrada de tabela de funções, bem como epílogos em cada saída para uma função. Para obter detalhes sobre o código de prólogo e de epílogo necessários em x64, confira Prólogo e epílogo x64.

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 estruturados e o comportamento de tratamento de exceção C++ no x64, consulte o tratamento de exceção x64.

Intrínsecos e assembly embutido

Uma das restrições para o compilador x64 não é um suporte de assembler embutido. Isso significa que as funções que não podem ser gravadas em C ou C++ precisarão ser gravadas como sub-rotinas ou como funções intrínsecas compatíveis com o compilador. Determinadas 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 compatíveis com o compilador são descritos 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 lidar com os dados de imagem estáticos. Esses dados incluem a tabela de endereços de importação, constantes de cadeia de caracteres, dados globais estáticos e assim por diante.

Confira também

Convenções de chamada