Çağrı yığını aynı zamanda çerçeve yığını olarak da adlandırılabilir. LIFO ilkesinden sonra yığınlanan
şeyler yerel değişkenler değil, çağrılan işlevlerin tüm yığın çerçeveleridir ("çağrılar") . Yerel değişkenler , sırasıyla, sözde işlev önsözü ve sonsözde bu çerçevelerle birlikte itilir ve çıkarılır .
Çerçevenin içinde değişkenlerin sırası tamamen belirtilmemiştir; Derleyiciler , hizalamalarını optimize etmek için çerçeve içindeki yerel değişkenlerin konumlarını uygun şekilde "yeniden sıralar", böylece işlemci onları olabildiğince çabuk getirebilir. Önemli olan, değişkenlerin bazı sabit adreslere göre ofsetinin çerçevenin ömrü boyunca sabit olmasıdır - bu nedenle, bir çapa adresi, örneğin çerçevenin kendisinin adresini almak ve bu adresin ofsetleriyle çalışmak yeterlidir. değişkenler. Böyle bir çapa adresi aslında sözde taban veya çerçeve işaretçisinde bulunurEBP kaydında saklanır. Öte yandan ofsetler, derleme zamanında açıkça bilinir ve bu nedenle makine koduna kodlanır.
Wikipedia'dan alınan bu grafik , tipik çağrı yığınının 1 gibi yapılandırıldığını gösterir :
Çerçeve işaretçisinde bulunan adrese erişmek istediğimiz değişkenin ofsetini ekleyin ve değişkenimizin adresini alıyoruz. Kısaca söylemek gerekirse, kod onlara doğrudan temel işaretçiden sabit derleme zamanı uzaklıkları aracılığıyla erişir; Basit işaretçi aritmetiği.
Misal
#include <iostream>
int main()
{
char c = std::cin.get();
std::cout << c;
}
gcc.godbolt.org bize
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl std::cin, %edi
call std::basic_istream<char, std::char_traits<char> >::get()
movb %al, -1(%rbp)
movsbl -1(%rbp), %eax
movl %eax, %esi
movl std::cout, %edi
call [... the insertion operator for char, long thing... ]
movl $0, %eax
leave
ret
.. için main
. Kodu üç alt bölüme ayırdım. İşlev prologu ilk üç işlemden oluşur:
- Temel işaretçi yığının üzerine itilir.
- Yığın işaretçisi temel işaretçiye kaydedilir
- Yığın işaretçisi yerel değişkenlere yer açmak için çıkarılır.
Daha sonra cin
EDI kaydı 2'ye taşınır ve get
çağrılır; Dönüş değeri EAX'tedir.
Çok uzak çok iyi. Şimdi ilginç olan şey oluyor:
EAX'in 8 bitlik kayıt AL tarafından belirlenen düşük sıralı baytı alınır ve temel işaretçinin hemen ardından baytta saklanır : Yani, -1(%rbp)
temel işaretçinin ofseti olur -1
. Bu bayt bizim değişkenimizdirc
. Ofset negatiftir çünkü yığın x86'da aşağı doğru büyür. Bir sonraki işlem depolar c
eax'a: EAX ESI taşınır cout
ile EDI taşınır ve sonra ekleme operatör olarak adlandırılır cout
ve c
bağımsız değişkenler olarak.
En sonunda,
- Dönüş değeri
main
EAX: 0'da saklanır. Bunun nedeni örtük return
ifadedir. Bunun xorl rax rax
yerine de görebilirsiniz movl
.
- ayrılın ve çağrı sitesine geri dönün.
leave
bu sonsözü kısaltıyor ve dolaylı olarak
- Yığın işaretçisini temel işaretçiyle değiştirir ve
- Temel işaretçiyi açar.
Bu işlemden sonra ve ret
gerçekleştirildikten sonra, çerçeve etkin bir şekilde açıldı, ancak arayanın cdecl arama kuralını kullanırken hala argümanları temizlemesi gerekiyor. Stdcall gibi diğer kurallar, aranan ucun, örneğin bayt miktarını 'a aktararak toparlanmasını gerektirir ret
.
Çerçeve İşaretçisi Eksikliği
Temel / çerçeve işaretçisinden değil, bunun yerine yığın işaretçisinden (ESB) ofsetleri kullanmak da mümkündür. Bu, aksi takdirde çerçeve işaretçisi değerini içerecek olan EBP kaydını keyfi kullanım için kullanılabilir hale getirir - ancak bazı makinelerde hata ayıklamayı imkansız hale getirebilir ve bazı işlevler için dolaylı olarak kapatılacaktır . Özellikle x86 dahil olmak üzere yalnızca birkaç yazmaçlı işlemciler için derleme yaparken kullanışlıdır.
Bu optimizasyon FPO (çerçeve işaretçisi ihmali) olarak bilinir ve -fomit-frame-pointer
GCC ve -Oy
Clang'da tarafından ayarlanır ; her optimizasyon seviyesi> 0 tarafından dolaylı olarak tetiklendiğine dikkat edin, ancak ve ancak hata ayıklama hala mümkünse, bunun dışında herhangi bir maliyeti yoktur. Daha fazla bilgi için buraya ve buraya bakın .
1 Yorumlarda belirtildiği gibi, çerçeve işaretçisinin muhtemelen dönüş adresinden sonraki adresi göstermesi amaçlanmıştır.
2 R ile başlayan yazmaçların, E ile başlayanların 64-bit karşılıkları olduğuna dikkat edin. EAX, RAX'in dört düşük sıralı baytını belirtir. Netlik için 32 bitlik yazmaçların adlarını kullandım.