C'de bazı eğitim materyalleri hazırlıyorum ve örneklerimin tipik yığın modeline uymasını istiyorum.
Linux, Windows, Mac OSX (PPC ve x86), Solaris ve en yeni Unix'lerde bir C yığını hangi yönde büyür?
C'de bazı eğitim materyalleri hazırlıyorum ve örneklerimin tipik yığın modeline uymasını istiyorum.
Linux, Windows, Mac OSX (PPC ve x86), Solaris ve en yeni Unix'lerde bir C yığını hangi yönde büyür?
Yanıtlar:
Yığın büyümesi genellikle işletim sisteminin kendisine değil, üzerinde çalıştığı işlemciye bağlıdır. Örneğin Solaris, x86 ve SPARC üzerinde çalışır. Mac OSX (bahsettiğiniz gibi) PPC ve x86 üzerinde çalışır. Linux, işteki büyük dostum System z'den cılız küçük bir kol saatine kadar her şeyde çalışıyor .
CPU herhangi bir seçenek sunuyorsa, işletim sistemi tarafından kullanılan ABI / çağrı kuralı, kodunuzun diğer herkesin kodunu çağırmasını istiyorsanız hangi seçimi yapmanız gerektiğini belirtir.
İşlemciler ve yönleri:
Son birkaçında yaşımı gösteren 1802, erken mekikleri kontrol etmek için kullanılan çipti (kapıların açık olup olmadığını algılamak, sanırım sahip olduğu işlem gücüne dayanarak :-) ve ikinci bilgisayarım COMX-35 ( ZX80'imi takip ederek ).
PDP11 panoda ayrıntıları buradan itibaren, 8051 ayrıntıları burada .
SPARC mimarisi, kayan pencere kayıt modeli kullanır. Mimari olarak görülebilen ayrıntılar, aynı zamanda, geçerli olan ve dahili olarak önbelleğe alınan, üst / alt akış durumunda tuzaklarla birlikte, kayıt pencerelerinin dairesel bir arabelleğini içerir. Ayrıntılar için buraya bakın. As SPARCv8 manuel açıklıyor , TASARRUF ve RESTORE talimatları ADD talimatları gibidir artı kayıt pencere rotasyonunu. Normal negatif yerine pozitif bir sabit kullanmak, yukarı doğru büyüyen bir yığın sağlar.
Yukarıda bahsedilen SCRT tekniği başka bir tekniktir - 1802, SCRT için bazılarını veya on altı 16-bitlik kayıtlarını kullandı (standart çağrı ve dönüş tekniği). Biri program sayacıydı, SEP Rn
talimatla herhangi bir kaydı PC olarak kullanabilirsiniz . Biri yığın göstericisiydi ve ikisi her zaman SCRT kod adresini gösterecek şekilde ayarlandı, biri çağrı için, biri dönüş için. Hiçbir kayıt özel bir şekilde ele alınmadı. Unutmayın, bu detaylar hafızadan geliyor, tamamen doğru olmayabilirler.
Örneğin, R3 PC, R4 SCRT çağrı adresi, R5 SCRT dönüş adresi ve R2 "yığın" ise (yazılımda uygulandığı şekliyle tırnak işaretleri), SEP R4
R4'ü PC olarak ayarlar ve SCRT'yi çalıştırmaya başlar çağrı kodu.
Daha sonra, R2, bir "yığın" (R6nın geçici depolama için kullanılan düşünüyorum) ile R3 depolamak o kadar ayarlama ya da aşağı, R3, aşağıdaki iki bayt kapmak, bunları yüklenmektedir içine daha sonra yapılacak, R3 SEP R3
ve yeni adresteki çalışmalıdır.
Geri dönmek için, SEP R5
eski adresi R2 yığınından çeker, ona iki tane ekler (çağrının adres baytlarını atlamak için), R3'e yükler SEP R3
ve önceki kodu çalıştırmaya başlar.
Tüm 6502/6809 / z80 yığın tabanlı kodlardan sonra başınızı döndürmek çok zor, ancak yine de kafanızı duvara çarpacak şekilde zarif. Ayrıca, yonganın en büyük satış özelliklerinden biri, bunlardan 7'sini (SCRT için 5, DMA için iki ve bellekten kesinti) hemen kaybetmenize rağmen, 16 16 bitlik kayıtlardan oluşan eksiksiz bir paketti. Ahh, pazarlamanın gerçekliğe karşı zaferi :-)
System z aslında çağrı / dönüş için R14 ve R15 kayıtlarını kullanarak oldukça benzerdir.
C ++ 'da (C'ye uyarlanabilir) stack.cc :
static int
find_stack_direction ()
{
static char *addr = 0;
auto char dummy;
if (addr == 0)
{
addr = &dummy;
return find_stack_direction ();
}
else
{
return ((&dummy > addr) ? 1 : -1);
}
}
static
. Bunun yerine, adresi yinelemeli bir çağrıya argüman olarak iletebilirsiniz.
static
, bunu birden fazla ararsanız, sonraki çağrılar başarısız olabilir ...
Büyümenin avantajı, eski sistemlerde yığın tipik olarak belleğin en üstündeydi. Programlar tipik olarak alttan başlayarak belleği doldurdu, bu nedenle bu tür bir bellek yönetimi, yığının altını ölçmek ve makul bir yere yerleştirmek ihtiyacını en aza indirdi.
MIPS ve birçok modern olarak RISC mimarileri (PowerPC, RISC-V, SPARC ... gibi) hiçbir vardır push
ve pop
talimatlar. Bu işlemler, yığın işaretçisini manuel olarak ayarlayarak ve ardından değeri ayarlanan işaretçiye göre göreceli olarak yükleyerek / depolayarak açıkça yapılır. Tüm yazmaçlar (sıfır yazmacı hariç) genel amaçlıdır, bu nedenle teoride herhangi bir kayıt bir yığın işaretçisi olabilir ve yığın , programcının istediği herhangi bir yönde büyüyebilir.
Bununla birlikte, yığın, muhtemelen yığın ve program verilerinin veya yığın verilerinin büyüdüğü ve birbiriyle çatıştığı durumdan kaçınmak için çoğu mimaride büyür. Ayrıca sh-'nin cevabından bahseden harika adresleme nedenleri var . Bazı örnekler: MIPS ABI'ler aşağı doğru büyür ve yığın işaretçisi olarak $29
(AKA $sp
) kullanır , RISC-V ABI da aşağı doğru büyür ve yığın işaretçisi olarak x2'yi kullanır
Intel 8051'de yığın büyür, muhtemelen bellek alanı çok küçük olduğundan (orijinal sürümde 128 bayt) yığın yoktur ve yığın büyümesinden ayrılması için yığını en üste koymanıza gerek yoktur. Alttan
Çeşitli mimarilerde yığın kullanımı hakkında daha fazla bilgiyi https://en.wikipedia.org/wiki/Calling_convention adresinde bulabilirsiniz.
Ayrıca bakınız
Görebildiğim kadarıyla bu noktaya değinmeyen diğer cevaplara sadece küçük bir ekleme:
Yığının aşağı doğru büyümesi, yığın içindeki tüm adreslerin yığın işaretçisine göre pozitif bir kaymaya sahip olmasını sağlar. Negatif kaydırmalara gerek yoktur, çünkü bunlar yalnızca kullanılmayan yığın alanına işaret ederler. Bu, işlemci yığın işaretleyiciye göre adreslemeyi desteklediğinde yığın konumlarına erişimi kolaylaştırır.
Çoğu işlemcinin, bazı kayıtlara göre yalnızca pozitif bir kayma ile erişime izin veren talimatları vardır. Bunlar birçok modern mimariyi ve bazı eski mimarileri içerir. Örneğin, ARM Başparmak ABI, tek bir 16 bitlik talimat sözcüğü içinde kodlanmış bir pozitif ofset ile yığın işaretleyiciye göre erişim sağlar.
Yığın yukarı doğru büyürse, yığın işaretleyiciye göre tüm yararlı kaydırmalar negatif olur, bu daha az sezgiseldir ve daha az uygundur. Aynı zamanda, örneğin bir yapının alanlarına erişim için, diğer yazmaç-göreceli adresleme uygulamaları ile çelişmektedir.
Çoğu sistemde yığın büyüyor ve https://gist.github.com/cpq/8598782 adresindeki makalem NEDEN büyüdüğünü açıklıyor. Çok basit: Sabit bir bellek yığınında büyüyen iki bellek bloğu (yığın ve yığın) nasıl düzenlenir? En iyi çözüm, onları zıt uçlara koymak ve birbirlerine doğru büyümelerine izin vermektir.
Programa ayrılan bellekte "kalıcı veriler" yani programın kodunun altta, ardından ortada yığının olması nedeniyle küçülür. Yığını referans almak için başka bir sabit noktaya ihtiyacınız var, böylece sizi en üstte bırakır. Bu, yığının potansiyel olarak öbek üzerindeki nesnelere bitişik oluncaya kadar büyüyeceği anlamına gelir.
Bu makro, UB olmadan çalışma zamanında onu algılamalıdır:
#define stk_grows_up_eh() stk_grows_up__(&(char){0})
_Bool stk_grows_up__(char *ParentsLocal);
__attribute((__noinline__))
_Bool stk_grows_up__(char *ParentsLocal) {
return (uintptr_t)ParentsLocal < (uintptr_t)&ParentsLocal;
}