Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
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
}
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
}
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
}
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
}
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.