Partilhar via


Uso da pilha x64

Toda a memória além do endereço atual do RSP é considerada volátil: o sistema operacional ou o depurador pode substituir essa memória durante uma sessão de depuração do usuário ou um manipulador de interrupção. Portanto, o RSP deve ser sempre definido antes de tentar ler ou gravar valores em um quadro de pilha.

Esta seção discute a alocação de espaço de pilha para variáveis locais e a alloca intrínseca.

Alocação da pilha

O prólogo da função é responsável por alocar espaço de pilha para variáveis locais, registros salvos, parâmetros de pilha e parâmetros de registro.

A área de parâmetro está sempre na parte inferior da pilha (mesmo se alloca for usada), de modo que ela sempre será adjacente ao endereço retornado durante qualquer chamada de função. Ela contém pelo menos quatro entradas, mas sempre com espaço suficiente para manter todos os parâmetros necessários para qualquer função que possa ser chamada. Observe que o espaço é sempre alocado para os parâmetros de registro, mesmo que os próprios parâmetros nunca sejam hospedados na pilha; um chamador tem a garantia de que o espaço foi alocado para todos os seus parâmetros. Os endereços residencial são necessários para os argumentos de registro para que uma área contígua esteja disponível caso a função chamada precise usar o endereço da lista de argumentos (va_list) ou um argumento individual. Essa área também fornece um lugar conveniente para salvar argumentos de registro durante a execução de conversão e como uma opção de depuração (por exemplo, facilita a localização dos argumentos durante a depuração se eles forem armazenados em seus endereços residenciais no código de prólogo). Mesmo que a função chamada tenha menos de quatro parâmetros, esses quatro locais de pilha são efetivamente pertencentes à função chamada e podem ser usados pela função chamada para outras finalidades, além de salvar valores de registro de parâmetro. Portanto, o chamador não pode salvar informações nessa região da pilha em uma chamada de função.

Se o espaço for alocado dinamicamente (alloca) em uma função, um registro não volátil deverá ser usado como um ponteiro de quadro para marcar a base da parte fixa da pilha e esse registro deverá ser salvo e inicializado no prólogo. Observe que, quando alloca é usado, as chamadas para o mesmo chamador do mesmo chamador podem ter endereços residencial diferentes nos seus parâmetros de registro.

A pilha sempre será mantida alinhada a 16 bytes, exceto dentro do prólogo (por exemplo, depois que o endereço de retorno for enviado por push) e, exceto quando indicado em Tipos de Função para uma determinada classe de funções de quadro.

Veja a seguir um exemplo do layout de pilha em que a função A chama uma função não folha B. O prólogo da Função A já alocou espaço para todos os parâmetros de registro e pilha exigidos por B na parte inferior da pilha. A chamada envia por push o endereço de retorno e o prólogo B aloca espaço para suas variáveis locais, registros não voláteis e o espaço necessário para ele chamar funções. Se B usar alloca, o espaço será alocado entre a área de salvamento do registro de variável local/não volátil e a área de pilha de parâmetros.

Diagram of the stack layout for the x64 conversion example.

Quando a função B chama outra função, o endereço de retorno é enviado por push logo abaixo do endereço residencial para RCX.

Construção da área de pilha de parâmetros dinâmicos

Se um ponteiro de quadro for usado, a opção existirá para criar dinamicamente a área de pilha de parâmetros. No momento, isso não é feito no compilador x64.

Tipos de função

Existem basicamente dois tipos de funções. Uma função que requer um quadro de pilha é chamada de função de quadro. Uma função que não requer um quadro de pilha é chamada de função folha.

Uma função de quadro é uma função que aloca espaço de pilha, chama outras funções, salva registros não voláteis ou usa tratamento de exceção. Ela também requer uma entrada de tabela de funções. Uma função de quadro requer um prólogo e um epílogo. Uma função de quadro pode alocar dinamicamente o espaço de pilha e pode empregar um ponteiro de quadro. Uma função de quadro tem as funcionalidades completas desse padrão de chamada à sua disposição.

Se uma função de quadro não chamar outra função, não será necessário alinhar a pilha (referenciada na Alocação de Pilha de Seção).

Uma função folha é aquela que não requer uma entrada de tabela de funções. Ela não pode fazer alterações em nenhum registro não volátil, incluindo RSP, o que significa que ela não pode chamar nenhuma função ou alocar espaço de pilha. Ele tem permissão para deixar a pilha desalinhada enquanto ela é executada.

Alinhamento de malloc

Malloc tem a garantia de retornar memória adequadamente alinhada para armazenar qualquer objeto que tenha um alinhamento fundamental e que possa caber na quantidade de memória alocada. Um alinhamento fundamental é um alinhamento menor ou igual ao maior alinhamento suportado pela implementação sem uma especificação de alinhamento. (No Visual C++, esse é o alinhamento necessário para um double ou oito bytes. No código destinado a plataformas de 64 bits, são 16 bytes). Por exemplo, uma alocação de quatro bytes seria alinhada em um limite que dá suporte a qualquer objeto de quatro bytes ou menor.

O Visual C++ permite tipos que têm alinhamento estendido, que também são conhecidos como tipos alinhados demais. Por exemplo, os tipos SSE __m128 e __m256, e os tipos declarados usando __declspec(align( n )) em que n é maior que oito, têm alinhamento estendido. O alinhamento de memória em um limite adequado para um objeto que requer alinhamento estendido não é garantido por malloc. Para alocar memória para tipos alinhados demais, use _aligned_malloc e funções relacionadas.

alloca

_alloca é necessário para ser alinhado em 16 bytes e, além disso, necessário para usar um ponteiro de quadro.

A pilha alocada precisa incluir espaço depois dela para parâmetros de funções chamadas posteriormente, conforme discutido na Alocação de Pilhas.

Confira também

Convenções de software x64
align
__declspec