stdcall ve cdecl


92

(Diğerlerinin yanı sıra) iki tür arama kuralı vardır - stdcall ve cdecl . Onlarla ilgili birkaç sorum var:

  1. Bir cdecl işlevi çağrıldığında, arayan kişi yığını boşaltması gerekip gerekmediğini nasıl anlar? Arama sitesinde, arayan kişi çağrılan işlevin cdecl mi yoksa stdcall işlevi mi olduğunu biliyor mu? O nasıl çalışır ? Arayan kişi desteyi boşaltması gerekip gerekmediğini nasıl anlar? Yoksa bağlayıcıların sorumluluğu mu?
  2. Stdcall olarak bildirilen bir işlev bir işlevi çağırırsa (cdecl olarak çağırma kuralı vardır) veya tam tersi, bu uygunsuz olur mu?
  3. Genel olarak, hangi aramanın daha hızlı olacağını söyleyebilir miyiz - cdecl veya stdcall?

9
Pek çok arama geleneği vardır ve bunların sadece ikisi vardır. en.wikipedia.org/wiki/X86_calling_conventions
Mooing Duck

1
Lütfen doğru cevabı işaretleyin
ceztko

Yanıtlar:


79

Raymond Chen ne güzel bir genel bakış sağlar __stdcallve __cdeclyapar .

(1) Arayan, bir işlevi çağırdıktan sonra yığını temizlemeyi "bilir", çünkü derleyici o işlevin çağırma kuralını bilir ve gerekli kodu üretir.

void __stdcall StdcallFunc() {}

void __cdecl CdeclFunc()
{
    // The compiler knows that StdcallFunc() uses the __stdcall
    // convention at this point, so it generates the proper binary
    // for stack cleanup.
    StdcallFunc();
}

Çağıran kuralı şu şekilde eşleştirmek mümkündür :

LRESULT MyWndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam);
// ...
// Compiler usually complains but there's this cast here...
windowClass.lpfnWndProc = reinterpret_cast<WNDPROC>(&MyWndProc);

Pek çok kod örneği bu kadar yanlış anlıyor, bu komik bile değil. Şöyle olması gerekiyordu:

// CALLBACK is #define'd as __stdcall
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg
    WPARAM wParam, LPARAM lParam);
// ...
windowClass.lpfnWndProc = &MyWndProc;

Bununla birlikte, programcının derleyici hatalarını göz ardı etmediğini varsayarsak, derleyici, ilgili işlevlerin çağırma kurallarını bileceği için yığını düzgün bir şekilde temizlemek için gereken kodu üretecektir.

(2) Her iki yol da çalışmalıdır. Aslında, bu, en azından Windows API ile etkileşime giren kodda oldukça sık görülür, çünkü __cdeclVisual C ++ derleyicisine göre C ve C ++ programları için öntanımlıdır ve WinAPI işlevleri __stdcallkuralı kullanır .

(3) İkisi arasında gerçek bir performans farkı olmamalıdır.


İyi bir örnek için +1 ve Raymond Chen'in kongre tarihini arama hakkındaki gönderisi. İlgilenen herkes için bunun diğer kısımları da iyi bir okuma.
OregonGhost

Raymond Chen için +1. Btw (OT): Blog arama kutusunu kullanarak neden diğer parçaları bulamıyorum? Google onları buluyor, ancak MSDN Blogları bulamıyor mu?
Nordic Mainframe

44

CDECL argümanları yığına ters sırayla itilir, çağıran yığını temizler ve sonuç işlemci kayıt defteri aracılığıyla döndürülür (daha sonra "kayıt A" olarak adlandıracağım). STDCALL'da bir fark vardır, arayan kişi yığını temizlemez, arama yapar.

Hangisinin daha hızlı olduğunu soruyorsunuz. Hiç kimse. Mümkün olduğu kadar yerel arama kuralını kullanmalısınız. Kuralı yalnızca bir çıkış yolu yoksa, belirli bir kuralın kullanılmasını gerektiren harici kitaplıkları kullanırken değiştirin.

Ayrıca, derleyicinin varsayılan olarak seçebileceği başka kurallar da vardır, yani Visual C ++ derleyicisi, işlemci kayıtlarının daha kapsamlı kullanımı nedeniyle teorik olarak daha hızlı olan FASTCALL kullanır.

Genellikle, bazı harici kitaplıklara iletilen geri arama işlevlerine uygun bir arama kuralı imzası vermelisiniz, yani qsortC kitaplığından geri arama CDECL olmalıdır (derleyici varsayılan olarak başka bir kural kullanıyorsa geri aramayı CDECL olarak işaretlemeliyiz) veya çeşitli WinAPI geri aramaları STDCALL (WinAPI'nin tamamı STDCALL'dur).

Diğer olağan durumlar, bazı harici işlevlere işaretçiler depoladığınızda olabilir, yani WinAPI işlevine bir işaretçi oluşturmak için, tür tanımının STDCALL ile işaretlenmesi gerekir.

Aşağıda, derleyicinin bunu nasıl yaptığını gösteren bir örnek verilmiştir:

/* 1. calling function in C++ */
i = Function(x, y, z);

/* 2. function body in C++ */
int Function(int a, int b, int c) { return a + b + c; }

CDECL:

/* 1. calling CDECL 'Function' in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call (jump to function body, after function is finished it will jump back here, the address where to jump back is in registers)
move contents of register A to 'i' variable
pop all from the stack that we have pushed (copy of x, y and z)

/* 2. CDECL 'Function' body in pseudo-assembler */
/* Now copies of 'a', 'b' and 'c' variables are pushed onto the stack */
copy 'a' (from stack) to register A
copy 'b' (from stack) to register B
add A and B, store result in A
copy 'c' (from stack) to register B
add A and B, store result in A
jump back to caller code (a, b and c still on the stack, the result is in register A)

STDCALL:

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */
pop 'a' from stack to register A
pop 'b' from stack to register B
add A and B, store result in A
pop 'c' from stack to register B
add A and B, store result in A
jump back to caller code (a, b and c are no more on the stack, result in register A)

Not: __fastcall, __cdecl'den daha hızlıdır ve STDCALL, Windows 64-bit için varsayılan arama kuralıdır
dns

ohh; bu yüzden dönüş adresini açmalı, bağımsız değişken blok boyutunu eklemeli ve daha önce atılan dönüş adresine atlamalı mı? Yığını temizlemediğiniz soruna sizi geri getirerek, yığında beklemek için kendinizi geri gömmeniz gerekir).
Dmitry

alternatif olarak, pop reg1'e dönün, yığın işaretçisini temel işaretçiye ayarlayın, ardından reg1'e atlayın
Dmitry

alternatif olarak, yığın işaretçisi değerini yığının üstünden aşağıya taşıyın, temizleyin, sonra ret'i çağırın
Dmitry

15

Sana bir çağrı eğer önemli değil demek bir ilanı fark __stdcallbir mesafede __cdecltam tersi ya. Öyle.

Nedeni: __cdeclÇağrılan fonksiyonlara iletilen argümanlar çağıran fonksiyon __stdcalltarafından yığından kaldırılır, argümanlar çağrılan fonksiyon tarafından yığından kaldırılır. A __cdeclile bir işlevi çağırırsanız __stdcall, yığın hiç temizlenmez, bu nedenle sonunda __cdeclargümanlar için yığın tabanlı bir başvuru kullandığında veya dönüş adresi geçerli yığın işaretçisindeki eski verileri kullanacaktır. Eğer bir ararsanız __stdcallbir gelen işlevi __cdecl, __stdcallfonksiyonu yığın argümanları temizler ve sonra __cdeclfonksiyon muhtemelen çağırarak fonksiyonları bilgileri döndürmek kaldırarak, yine öyle.

Microsoft'un C kuralı, isimleri karıştırarak bunu aşmaya çalışır. Bir __cdeclişlevin önünde alt çizgi bulunur. Bir __stdcallişlev, alt çizgi ile ön ekler ve "@" işareti ve kaldırılacak bayt sayısı ile son eklenmiştir. Örneğin, __cdeclF (x) olarak bağlı olan _f, __stdcall f(int x)birbirleriyle bağlantılı olup _f@4burada sizeof(int)) 4 bayt

Bağlayıcıyı geçmeyi başarırsanız, hata ayıklama karmaşasının tadını çıkarın.


3

@ Adf88'in cevabını geliştirmek istiyorum. STDCALL sözde kodunun gerçekte nasıl gerçekleştiğini yansıtmadığını hissediyorum. İşlev gövdesindeki yığından 'a', 'b' ve 'c' çıkarılmaz. Bunun yerine , bir çırpıda arayan kişiye geri atlayan ve aynı zamanda yığından "a", "b" ve "c" yi çıkaran rettalimat ( ret 12bu durumda kullanılacak) tarafından patlatılırlar .

İşte benim versiyonum anlayışıma göre düzeltildi:

STDCALL:

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then copy of 'y', then copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */ copy 'a' (from stack) to register A copy 'b' (from stack) to register B add A and B, store result in A copy 'c' (from stack) to register B add A and B, store result in A jump back to caller code and at the same time pop 'a', 'b' and 'c' off the stack (a, b and c are removed from the stack in this step, result in register A)


2

İşlev türünde belirtilir. Bir işlev göstericiniz olduğunda, açıkça stdcall değilse bunun cdecl olduğu varsayılır. Bu, bir stdcall işaretçisi ve bir cdecl işaretçisi alırsanız, bunları değiştiremeyeceğiniz anlamına gelir. İki işlev türü birbirini sorunsuz olarak arayabilir, diğerini beklediğinizde yalnızca bir türü elde eder. Hıza gelince, ikisi de aynı rolleri oynuyor, sadece çok az farklı bir yerde, gerçekten alakasız.


1

Arayan ve arayan ucun, çağrı noktasında aynı kuralı kullanması gerekir - güvenilir bir şekilde çalışabilmesinin tek yolu budur. Hem arayan hem de aranan uç, önceden tanımlanmış bir protokolü izler - örneğin, yığını temizlemesi gereken kişi. Kurallar uyuşmazsa, programınız tanımlanmamış bir davranışla karşılaşır - muhtemelen sadece olağanüstü bir şekilde çöker.

Bu yalnızca çağrı sitesi başına gereklidir - çağıran kodun kendisi herhangi bir çağrı kuralına sahip bir işlev olabilir.

Bu kurallar arasında performans açısından gerçek bir fark görmemelisiniz. Bu bir sorun haline gelirse, genellikle daha az arama yapmanız gerekir - örneğin, algoritmayı değiştirin.


1

Bunlar Derleyiciye ve Platforma özeldir. Ne C ne de C ++ standardı, C ++ dışında arama kuralları hakkında hiçbir şey söylemez extern "C".

arayan kişi yığını boşaltması gerekip gerekmediğini nasıl anlar?

Arayan, işlevin arama kuralını bilir ve aramayı buna göre yönetir.

Arama sitesinde, arayan kişi çağrılan işlevin cdecl mi yoksa stdcall işlevi mi olduğunu biliyor mu?

Evet.

O nasıl çalışır ?

İşlev bildiriminin bir parçasıdır.

Arayan kişi desteyi boşaltması gerekip gerekmediğini nasıl anlar?

Arayan, arama kurallarını bilir ve buna göre hareket edebilir.

Yoksa bağlayıcıların sorumluluğu mu?

Hayır, çağırma kuralı bir işlevin bildiriminin parçasıdır, bu nedenle derleyici bilmesi gereken her şeyi bilir.

Stdcall olarak bildirilen bir işlev bir işlevi çağırırsa (cdecl olarak çağırma kuralı vardır) veya tam tersi, bu uygunsuz olur mu?

Hayır. Neden olmasın?

Genel olarak, hangi aramanın daha hızlı olacağını söyleyebilir miyiz - cdecl veya stdcall?

Bilmiyorum. Dene.


0

a) Arayan tarafından bir cdecl işlevi çağrıldığında, arayan kişi yığını boşaltması gerekip gerekmediğini nasıl anlar?

cdeclModifiye işlev prototip parçası (ya da işlev işaretçisi türü vs.) arayan oradan bilgi almak ve buna göre hareket böyledir.

b) stdcall olarak bildirilen bir işlev bir işlevi çağırırsa (cdecl olarak çağırma kuralı vardır) veya tam tersi, bu uygun olmaz mıydı?

Hayır, bu iyi.

c) Genel olarak, hangi aramanın daha hızlı olacağını söyleyebilir miyiz - cdecl veya stdcall?

Genel olarak, bu tür ifadelerden kaçınırım. Ayrım önemlidir, örn. va_arg işlevlerini kullanmak istediğinizde. Teorik olarak, bu stdcalldaha hızlı olabilir ve daha küçük kodlar üretir çünkü argümanları patlatmayı yerelleri patlatmakla birleştirmeye izin verir, ancak OTOH ile cdecl, akıllıysanız aynı şeyi de yapabilirsiniz.

Daha hızlı olmayı amaçlayan arama kuralları genellikle bazı kayıt geçişleri yapar.


-1

Çağrı kurallarının C / C ++ programlama dilleriyle hiçbir ilgisi yoktur ve bir derleyicinin verilen dili nasıl uyguladığına dair daha spesifiktir. Sürekli olarak aynı derleyiciyi kullanıyorsanız, kuralları çağırma konusunda asla endişelenmenize gerek kalmaz.

Ancak, bazen farklı derleyiciler tarafından derlenen ikili kodun doğru şekilde birlikte çalışmasını isteriz. Bunu yaptığımızda Uygulama İkili Arayüzü (ABI) adı verilen bir şey tanımlamamız gerekir. ABI, derleyicinin C / C ++ kaynağını makine koduna nasıl dönüştürdüğünü tanımlar. Bu, arama kurallarını, adın karıştırılmasını ve v-tablo düzenini içerecektir. cdelc ve stdcall, x86 platformlarında yaygın olarak kullanılan iki farklı arama kuralı.

Çağırma kuralıyla ilgili bilgileri kaynak başlığına yerleştirerek, derleyici, verilen çalıştırılabilir dosya ile doğru şekilde birlikte çalışmak için hangi kodun üretilmesi gerektiğini bilecektir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.