Çağrı yığını tam olarak nasıl çalışır?
Programlama dilleri işlemleri nasıl çalıştığını ve özellikle OS/CPU ile etkileşim nasıl daha derin bir anlayış almak için çalışıyorum. Muhtemelen her her cevap Yığın Taşması/yığın ilgili konu burada yığın okudum, ve çok başarılılar. Ama hala henüz tam olarak anlayamadığım bir şey var.
Geçerli bir Pas kod eğiliminde olan sahte kod bu işlevi görür ;-)
fn foo() {
let a = 1;
let b = 2;
let c = 3;
let d = 4;
// line X
doSomething(a, b);
doAnotherThing(c, d);
}
Bu satırda X gibi görünmeye yığın sanırım:
Stack
a -------------
| 1 |
b -------------
| 2 |
c -------------
| 3 |
d -------------
| 4 |
-------------
Şimdi, yığın nasıl çalıştığı hakkında okuduğum her şey kesinlikle FIFO kuralları (son giren ilk çıkar) itaat eder. Gibi bir yığın veri türü .NET, Java veya başka bir programlama dili.
Ama eğer durum buysa, o zaman ne satırdan sonra X olur? Açıkçası, biz gereken bir sonraki şey a
b
ile çalışmak ama OS CPU/çünkü bu (?) d
c
pop için ilk a
b
geri dönmek. Ama sonra bir sonraki satıra c
d
ihtiyacı var çünkü ayak kendini vur.
Ben ne merak ediyorumtam olaraksahneler? geride olur
İlgili bir diğer soru. Bu gibi diğer işlevler için referans verdiğimiz düşünün
fn foo() {
let a = 1;
let b = 2;
let c = 3;
let d = 4;
// line X
doSomething(&a, &b);
doAnotherThing(c, d);
}
İşlerin nasıl olduğunu anlıyorum, bu doSomething
parametreler a
ve foo
19 *gibi aynı bellek adresi aslında şuna işaret ediyor demektir. Ama sonra tekrar, bu yok anlamına geliryığın a
b
varana kadar açılıroluyor.
Bu iki olgu bana tam olarak nasıl idrak edemediğimi düşünüyorumtam olarakbu kesinlikle takip nasıl çalışır yığını veFIFOkuralları.
CEVAP
Çağrı yığını da bir yığın çerçevesi olarak adlandırılabilir.
Olan şeyleryığılmışFIFO prensibine sonra bunun nasıl çalıştığını görmek için çağrıldığını yerel değişkenler ama tüm yığın çerçeveler. Yerel değişkenler ve sözde function prologue epilogue, sırasıyla, o çerçeve ile birlikte itti attı.
Çerçeve içindeki değişkenlerin sırasını tamamen belirsiz; Derleyiciler işlemciye onları mümkün olduğunca çabuk getir, böylece 46* *uygun hizalamayı optimize etmek için. Önemli bir gerçeğibu değişkenler bazı sabit adresi için göreli uzaklık çerçeve ömrü boyunca sabittir- bağlantı adresi al, karenin adresini kendisi söylüyor, ve değişkenler için adres uzaklıklar ile çalışmak için yeterlidir.
Bu tabii ki derleme zamanında bilinen ve bu nedenle makine kodu içine kodlanmış olan uzaklık.
Bu X gibi görünebilir:
Address value
0xFEFD return address
0xFF01 d ----------
| 1 |
0xFF05 c ----------
| 2 |
0xFF09 b ----------
| 3 |
0xFF0D a ----------
| 4 |
----------
X is down here.
Çapa adresimiz 0xFF01
olabilir.
Böyle bir bağlantı adresi aslında sözde yer alıyortabanyaçerçeve işaretçisiEBP kasada saklı olan.
Adresi Kare işaretçisi içerdiği için erişmek istediğimiz bir değişken mahsup ekleyin ve bizim değişkenin adresi. Kısa bir süre sonra, kodu direkt olarak onları base pointer dan derleme zamanı sabiti uzaklıklar üzerinden erişir; basit işaretçi aritmetiği olduğunu söyledi.
Örnek
#include <iostream>
int main()
{
char c = std::cin.get();
std::cout << c;
}
gcc.godbolt.org bize verir
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl std::cin, íi
call std::basic_istream<char, std::char_traits<char> >::get()
movb %al, -1(%rbp)
movsbl -1(%rbp), êx
movl êx, %esi
movl std::cout, íi
call [... the insertion operator for char, long thing... ]
movl $0, êx
leave
ret
main
... Üç bölümlere kodu ayırdım.
Fonksiyon Prolog ilk üç işlemleri oluşur:
- Temel işaretçi yığına itilir. Bu bizim dönüş adresi.
- Yığın göstergesi ana işaretçi kaydedilir
- Yığın işaretçisi yerel değişkenler için yer açmak için çıkarılır.
Sonra cin
EDİ kayıt içine taşınır1get
denir; dönüş değeri TÜR.
Şimdiye kadar çok iyi. Şimdi ilginç bir şey olur:
SES alt sıra bayt, 8-bit kayıt AL tarafından belirlenen alınırtemel işaretçiyi sonra bayt hakkı saklı: -1(%rbp)
temel işaretçi offset -1
.Bu bayt c
bizim değişkendir. Uzaklık 86 üzerinde aşağıya doğru büyür yığını nedeniyle olumsuz. Sonraki operasyon mağazaları c
SES: SES taşınır, ESI, cout
taşınır EDİ ve sonra Ekleme operatörü ile adlandırılır cout
c
olmak argümanlar.
Son olarak
main
dönüş değeri TÜR saklanır: 0.return
örtülü ifadesi yüzünden. Ayrıcaxorl rax rax
movl
yerine görebilirsiniz.- bırakın site dönün.
leave
bu son söz ve örtülü olarak biraz daha kısa.- Temel işaretçisini, işaretçi yığını ve değiştirir
- Babalık Bankası işaretçi.
İşaretçi İhmal Çerçeve
Ayrıca mümkündür. Aksi takdirde bu çerçeve işaretçi değeri keyfi olarak kullanılabilir-ama debugging impossible on some machines, yapabilirsiniz baskılarını EBP - kayıt yapar ve implicitly turned off for some functions olacak. 86 de dahil olmak üzere sadece birkaç kayıtları ile işlemci için derleme sırasında özellikle yararlıdır.
Bu optimizasyon FPO (çerçeve göstericisi ihmal) bilinen ve Çınlama GCC -fomit-frame-pointer
-Oy
tarafından ayarlanır; örtülü olarak her optimizasyon seviyesi ile tetiklenen olduğunu unutmayın >Eğer hata ayıklama herhangi bir masraf bunun dışında bir önemi yok, çünkü hala mümkün olup olmadığını, eğer 0 ve sadece.
Daha fazla bilgi için bkz: here ve here.
1R ile başlayan kayıtları 64-bit E. SES atar RAX alt sıra dört bayt ile başlayan olanlar meslektaşları olduğunu unutmayın. Netlik için 32-bit kaydeder adlarını kullandım.
Kilit tam olarak nasıl çalışır?...
SignalR DAHİLİ olarak nasıl çalışır?...
Kuyruk özyineleme tam olarak nasıl çal...
Nasıl veritabanı DAHİLİ olarak çalışır...
Teknik olarak, nasıl variadic fonksiyo...