Existem (entre outros) dois tipos de convenções de chamada - stdcall e cdecl . Eu tenho algumas perguntas sobre eles:
- Quando uma função cdecl é chamada, como um chamador sabe se ela deve liberar a pilha? No local da chamada, o chamador sabe se a função que está sendo chamada é cdecl ou uma função stdcall? Como funciona ? Como o chamador sabe se deve liberar a pilha ou não? Ou é a responsabilidade dos vinculadores?
- Se uma função declarada como stdcall chamar uma função (que tem uma convenção de chamada como cdecl), ou o contrário, isso seria inapropriado?
- Em geral, podemos dizer que chamada será mais rápida - cdecl ou stdcall?
Respostas:
Raymond Chen oferece uma boa visão geral do que
__stdcall
e__cdecl
faz .(1) O chamador "sabe" limpar a pilha depois de chamar uma função porque o compilador conhece a convenção de chamada dessa função e gera o código necessário.
É possível divergir da convenção de chamada , assim:
Tantas amostras de código erram, não é nem engraçado Deveria ser assim:
No entanto, presumindo que o programador não ignore os erros do compilador, o compilador gerará o código necessário para limpar a pilha de maneira adequada, pois conhecerá as convenções de chamada das funções envolvidas.
(2) Ambas as formas devem funcionar. Na verdade, isso acontece com bastante frequência, pelo menos no código que interage com a API do Windows, porque
__cdecl
é o padrão para programas C e C ++ de acordo com o compilador Visual C ++ e as funções WinAPI usam a__stdcall
convenção .(3) Não deve haver nenhuma diferença real de desempenho entre os dois.
fonte
Em CDECL, os argumentos são colocados na pilha em ordem reversa, o chamador limpa a pilha e o resultado é retornado por meio do registro do processador (posteriormente chamarei de "registro A"). Em STDCALL há uma diferença, o chamador não limpa a pilha, mas a chamada faz.
Você está perguntando qual é mais rápido. Ninguém. Você deve usar a convenção de chamada nativa o máximo que puder. Altere a convenção apenas se não houver saída, ao usar bibliotecas externas que requerem o uso de certa convenção.
Além disso, existem outras convenções que o compilador pode escolher como padrão, isto é, o compilador Visual C ++ usa FASTCALL, que é teoricamente mais rápido devido ao uso mais extensivo de registros do processador.
Normalmente, você deve fornecer uma assinatura de convenção de chamada adequada para funções de retorno de chamada passadas para alguma biblioteca externa, ou seja, o retorno de chamada
qsort
da biblioteca C deve ser CDECL (se o compilador por padrão usa outra convenção, então devemos marcar o retorno de chamada como CDECL) ou vários retornos de chamada WinAPI devem ser STDCALL (WinAPI inteiro é STDCALL).Outro caso comum pode ser quando você está armazenando ponteiros para algumas funções externas, por exemplo, para criar um ponteiro para a função WinAPI, sua definição de tipo deve ser marcada com STDCALL.
E abaixo está um exemplo que mostra como o compilador faz isso:
CDECL:
STDCALL:
fonte
Notei uma postagem que diz que não importa se você chama a
__stdcall
de a__cdecl
ou vice-versa. É verdade.O motivo: com
__cdecl
os argumentos que são passados para as funções chamadas são removidos da pilha pela função de chamada, em__stdcall
, os argumentos são removidos da pilha pela função chamada. Se você chamar uma__cdecl
função com um__stdcall
, a pilha não será limpa, então, eventualmente, quando o__cdecl
usar uma referência baseada em pilha para argumentos ou endereço de retorno usará os dados antigos no ponteiro da pilha atual. Se você chamar uma__stdcall
função de a__cdecl
, a__stdcall
função limpará os argumentos na pilha e, em seguida__cdecl
, fará isso novamente, possivelmente removendo as informações de retorno das funções de chamada.A convenção da Microsoft para C tenta contornar isso alterando os nomes. Uma
__cdecl
função é prefixada com um sublinhado. Uma__stdcall
função é prefixada com um sublinhado e sufixada com uma arroba “@” e o número de bytes a serem removidos. Por exemplo,__cdecl
f (x) está ligado como_f
,__stdcall f(int x)
está ligado como_f@4
ondesizeof(int)
está 4 bytes)Se você conseguir passar do vinculador, aproveite a bagunça de depuração.
fonte
Eu quero melhorar a resposta de @ adf88. Eu sinto que o pseudocódigo para o STDCALL não reflete a forma como isso acontece na realidade. 'a', 'b' e 'c' não são retirados da pilha no corpo da função. Em vez disso, eles são retirados pela
ret
instrução (ret 12
seria usada neste caso) que em uma só investida volta para o chamador e ao mesmo tempo retira 'a', 'b' e 'c' da pilha.Aqui está minha versão corrigida de acordo com o meu entendimento:
STDCALL:
fonte
É especificado no tipo de função. Quando você tem um ponteiro de função, ele é considerado cdecl, se não explicitamente stdcall. Isso significa que se você obtiver um ponteiro stdcall e um ponteiro cdecl, não poderá trocá-los. Os dois tipos de função podem chamar um ao outro sem problemas, basta obter um tipo quando você espera o outro. Quanto à velocidade, os dois desempenham as mesmas funções, apenas em um lugar ligeiramente diferente, é realmente irrelevante.
fonte
O chamador e o receptor precisam usar a mesma convenção no ponto de invocação - essa é a única maneira de funcionar de forma confiável. Tanto o chamador quanto o receptor seguem um protocolo predefinido - por exemplo, quem precisa limpar a pilha. Se as convenções não corresponderem, seu programa terá um comportamento indefinido - provavelmente travará de maneira espetacular.
Isso só é necessário por site de invocação - o próprio código de chamada pode ser uma função com qualquer convenção de chamada.
Você não deve notar nenhuma diferença real no desempenho entre essas convenções. Se isso se tornar um problema, você geralmente precisará fazer menos chamadas - por exemplo, altere o algoritmo.
fonte
Essas coisas são específicas do compilador e da plataforma. Nem o padrão C nem o C ++ dizem nada sobre as convenções de chamada, exceto
extern "C"
em C ++.O chamador conhece a convenção de chamada da função e trata a chamada de acordo.
Sim.
Faz parte da declaração da função.
O chamador conhece as convenções de chamada e pode agir de acordo.
Não, a convenção de chamada é parte da declaração de uma função para que o compilador saiba tudo o que precisa saber.
Não. Por que deveria?
Eu não sei. Teste-o.
fonte
O
cdecl
modificador é parte do protótipo da função (ou tipo de ponteiro de função, etc.) para que o chamador obtenha as informações de lá e aja de acordo.Não, está bem.
Em geral, eu me absteria de fazer tais declarações. A distinção importa, por exemplo. quando você deseja usar funções va_arg. Em teoria, pode ser que
stdcall
seja mais rápido e gere um código menor porque permite combinar o popping dos argumentos com o popping dos locais, mas com OTOHcdecl
, você pode fazer a mesma coisa também, se for inteligente.As convenções de chamada que visam ser mais rápidas geralmente fazem alguma passagem de registro.
fonte
As convenções de chamada não têm nada a ver com as linguagens de programação C / C ++ e são bastante específicas sobre como um compilador implementa a linguagem fornecida. Se você usa consistentemente o mesmo compilador, nunca precisa se preocupar com convenções de chamada.
No entanto, às vezes queremos código binário compilado por diferentes compiladores para interoperar corretamente. Quando fazemos isso, precisamos definir algo chamado Application Binary Interface (ABI). A ABI define como o compilador converte a fonte C / C ++ em código de máquina. Isso incluirá convenções de chamada, alteração de nome e layout de tabela v. cdelc e stdcall são duas convenções de chamada diferentes comumente usadas em plataformas x86.
Colocando as informações sobre a convenção de chamada no cabeçalho de origem, o compilador saberá qual código precisa ser gerado para interoperar corretamente com o executável fornecido.
fonte