C'de, parantezler bir yığın çerçeve gibi davranır mı?


153

Yeni bir küme parantezi kümesinde bir değişken oluşturursam, bu değişken kapanma ayracı üzerindeki yığından çıkmış mı, yoksa işlevin sonuna kadar takılıyor mu? Örneğin:

void foo() {
   int c[100];
   {
       int d[200];
   }
   //code that takes a while
   return;
}

Will dsırasında hafıza kaplıyor olabilir code that takes a whilebölümünde?


8
(1) Standardına göre, (2) uygulamalar arasında evrensel pratik mi, yoksa (3) uygulamalar arasında ortak pratik mi?
David Thornley

Yanıtlar:


83

Hayır, diş telleri yığın çerçevesi gibi davranmaz. C'de parantezler yalnızca bir adlandırma kapsamını belirtir, ancak hiçbir şey yok olmaz ve kontrol dışarı çıktığında yığıntan herhangi bir şey atmaz.

Bir programcı kod yazma olarak, bunu genellikle bir yığın çerçeve gibi düşünebilirsiniz. Diş telleri içinde bildirilen tanımlayıcılara yalnızca diş telleri içinde erişilebilir, bu nedenle bir programcının bakış açısından, bildirildikçe yığına itilir ve kapsamdan çıkıldığında patlar. Ancak, derleyiciler giriş / çıkışta herhangi bir şeyi iten / çıkaran kod oluşturmak zorunda değildir (ve genellikle yoktur).

Ayrıca, yerel değişkenlerin herhangi bir yığın alanı kullanamayabileceğini unutmayın: CPU kayıtlarında veya başka bir yardımcı depolama konumunda tutulabilir veya tamamen optimize edilebilir.

Böylece, ddizi, teorik olarak, tüm fonksiyon için bellek tüketebilir. Ancak, derleyici onu optimize edebilir veya belleğini, kullanım ömrü çakışmayan diğer yerel değişkenlerle paylaşabilir.


9
Bu uygulamaya özgü değil mi?
avakar

54
C ++ 'da, bir nesnenin yıkıcısı kapsamının sonunda çağrılır. Belleğin geri kazanılıp kazanılmayacağı uygulamaya özgü bir konudur.
Kristopher Johnson

8
@ pm100: Yıkıcılar aranacak. Bu, bu nesnelerin işgal ettiği bellek hakkında hiçbir şey söylemez.
Donal Fellows

9
C standardı, blokta bildirilen otomatik değişkenlerin ömrünün yalnızca bloğun yürütülmesi bitene kadar uzandığını belirtir. Yani esasen bu otomatik değişkenler do blok sonunda "yok" olsun.
caf

3
@KristopherJohnson: Bir yöntemin her biri 1Kbyte dizisi bildiren iki ayrı bloğu ve iç içe yöntem olarak adlandırılan üçüncü bir bloğu varsa, bir derleyici aynı diziyi her iki dizi için de kullanabilir ve / veya diziyi yerleştirebilir yığının en sığ kısmına yerleştirin ve yığın işaretçisini, iç içe yöntemi çağırarak üzerine getirin. Bu davranış, işlev çağrısı için gereken yığın derinliğini 2K azaltabilir.
supercat

39

Değişkenin gerçekte hafızayı kapladığı süre açıkça derleyiciye bağlıdır (ve birçok derleyici, iç bloklar işlevler içine girilip çıkıldığında yığın işaretçisini ayarlamaz).

Bununla birlikte, yakından ilişkili ancak muhtemelen daha ilginç bir soru, programın bu iç nesneye iç kapsamın dışında (ancak içeren işlev içinde) erişmesine izin verilip verilmediği, yani:

void foo() {
   int c[100];
   int *p;

   {
       int d[200];
       p = d;
   }

   /* Can I access p[0] here? */

   return;
}

(Başka bir deyişle: derleyici pratikte çoğu olmasa bile dağılmasına izin verilird mi?).

Cevap derleyici olmasıdır edilir ayırması için izin dve erişme p[0]açıklama gösterir nerede tanımsız davranış (programıdır değil erişmek için iç kapsamı iç nesne dışında izin verilir). C standardının ilgili kısmı 6.2.4p5'tir:

Değişken uzunlukta bir dizi tipine sahip olmayan böyle bir nesne [otomatik depolama süresine sahip olan] için , ömrü, ilişkili olduğu bloğa girişten, o bloğun yürütülmesi herhangi bir şekilde sona erene kadar uzar . (Kapalı bir bloğa girmek veya bir işlevi çağırmak, geçerli bloğun yürütülmesini askıya alır ancak bitmez.) Blok tekrar tekrar girilirse, her seferinde nesnenin yeni bir örneği oluşturulur. Nesnenin başlangıç ​​değeri belirsizdir. Nesne için bir başlatma belirtilirse, bloğun yürütülmesinde bildirime her ulaşıldığında gerçekleştirilir; aksi halde, beyana her ulaşıldığında değer belirsizleşir.


Daha yüksek seviyeli diller kullandıktan sonra yıllar ve kapsamın ve belleğin C ve C ++ 'da nasıl çalıştığını öğrenen biri olarak, bu cevabı kabul edilenden daha kesin ve kullanışlı buluyorum.
Chris

20

Sorunuz açık bir şekilde cevaplanacak kadar açık değil.

Bir yandan, derleyiciler iç içe blok kapsamları için normalde herhangi bir yerel bellek ayırma-dağıtma işlemi yapmazlar. Yerel bellek normalde fonksiyon girişinde sadece bir kez tahsis edilir ve fonksiyon çıkışında serbest bırakılır.

Öte yandan, yerel bir nesnenin ömrü sona erdiğinde, o nesne tarafından kullanılan bellek daha sonra başka bir yerel nesne için yeniden kullanılabilir. Örneğin, bu kodda

void foo()
{
  {
    int d[100];
  }
  {
    double e[20];
  }
}

her iki dizi genellikle aynı bellek alanını kaplar, yani işlev fooiçin gereken toplam yerel depolama miktarı , aynı anda ikisi için değil, iki dizinin en büyüğü için gerekli olan şeydir .

İkincisinin dsorunuz bağlamında fonksiyonun sonuna kadar hafızayı kullanmaya devam edip etmeyeceği karar vermeniz içindir.


6

Uygulamaya bağlıdır. Gcc 4.3.4'ün ne yaptığını test etmek için kısa bir program yazdım ve fonksiyonun başlangıcında tüm yığın alanını bir kerede ayırıyor. Gcc'nin oluşturduğu montajı -S bayrağını kullanarak inceleyebilirsiniz.


3

Resim d [] olacak olup rutin geri kalanı için yığın olabilir. Ancak alloca () farklıdır.

Düzenleme: Kristopher Johnson (ve simon ve Daniel) haklı ve ilk cevabım yanlıştı . CYGWIN'de gcc 4.3.4. ile kod:

void foo(int[]);
void bar(void);
void foobar(int); 

void foobar(int flag) {
    if (flag) {
        int big[100000000];
        foo(big);
    }
    bar();
}

verir:

_foobar:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $400000008, %eax
    call    __alloca
    cmpl    $0, 8(%ebp)
    je      L2
    leal    -400000000(%ebp), %eax
    movl    %eax, (%esp)
    call    _foo
L2:
    call    _bar
    leave
    ret

Yaşa ve öğren! Ve hızlı bir test, AndreyT'nin çoklu tahsisler için de doğru olduğunu gösteriyor.

Daha sonra eklendi : Yukarıdaki test gcc dokümantasyonunun tam olarak doğru olmadığını göstermektedir . Yıllarca şunları söyledi (vurgu eklendi):

Msgstr "Değişken uzunlukta bir dizinin alanı , dizi adının kapsamı biter bitmez dağıtılır ."


Optimizasyon devre dışı bırakıldığında derleme, optimize edilmiş kodda ne alacağınızı göstermez. Bu durumda, davranış aynıdır (işlevin başlangıcında ayrılır ve yalnızca işlevden ayrılırken ücretsizdir): godbolt.org/g/M112AQ . Ancak cygwin olmayan gcc bir allocaişlev çağırmaz . Cygwin gcc'nin bunu yapmasına gerçekten şaşırdım. Değişken uzunlukta bir dizi bile değil, bu yüzden IDK neden bunu ortaya çıkarıyorsunuz?
Peter Cordes

2

Olabilirler. Yapmayabilirler. Gerçekten ihtiyacın olduğunu düşündüğüm cevap: Hiçbir şey varsayma. Modern derleyiciler her türlü mimariyi ve uygulamaya özgü büyüyü yapar. Kodunuzu insanlara basit ve okunaklı bir şekilde yazın ve derleyicinin iyi şeyler yapmasına izin verin. Derleyicinin etrafını kodlamaya çalışırsanız sorun istiyorsunuz - ve bu durumlarda genellikle karşılaştığınız sorun genellikle korkunç ve teşhis edilmesi güçtür.


1

Değişkeniniz dgenellikle yığından çıkarılmaz. Kıvırcık parantezler yığın çerçevesini göstermez. Aksi takdirde, böyle bir şey yapamazsınız:

char var = getch();
    {
        char next_var = var + 1;
        use_variable(next_char);
    }

Kıvırcık parantezler gerçek bir yığın itme / pop'a neden olduysa (işlev çağrısı gibi), yukarıdaki kod derlenmeyecektir, çünkü parantez içindeki kod parantezlerin vardışında yaşayan değişkene erişemeyecektir (tıpkı bir alt- işlev çağırma işlevindeki değişkenlere doğrudan erişemez). Bunun böyle olmadığını biliyoruz.

Kıvırcık parantezler sadece kapsam belirleme için kullanılır. Derleyici, kapalı parantezlerin dışından "iç" değişkenine herhangi bir erişimi geçersiz olarak ele alacaktır ve bu belleği başka bir şey için yeniden kullanabilir (uygulamaya bağlıdır). Ancak, kapatma işlevi dönünceye kadar yığından kaldırılamayabilir.

Güncelleme: İşte C özelliklerinin söyledikleri. Otomatik saklama süresine sahip nesneler hakkında (bölüm 6.4.2):

Değişken uzunlukta bir dizi türüne sahip olmayan bir nesne için, kullanım ömrü ilişkili olduğu bloğa girişten, o bloğun yürütülmesi yine de sona erene kadar uzar.

Aynı bölüm "ömür boyu" terimini (benimkini vurgulayan) olarak tanımlar:

Süresi bir nesnenin depolanması olan sırasında program yürütme kısmıdır garanti bunun için rezerve edilecek. Bir nesne vardır, sabit bir adrese sahiptir ve son saklanan değerini kullanım ömrü boyunca korur. Bir nesnenin kullanım ömrü dışında belirtilmesi durumunda, davranış tanımsızdır.

Buradaki anahtar kelime elbette 'garantilidir'. İç küme parantezinin kapsamından ayrıldıktan sonra dizinin ömrü sona erer. Depolama alanı bunun için ayrılabilir veya atanmayabilir (derleyiciniz alanı başka bir şey için yeniden kullanabilir), ancak diziye erişme girişimleri tanımlanmamış davranışları tetikler ve öngörülemeyen sonuçlar doğurur.

C spec'te yığın çerçeve kavramı yoktur. Yalnızca sonuçta ortaya çıkan programın nasıl davranacağından bahseder ve uygulama ayrıntılarını derleyiciye bırakır (sonuçta, uygulama yığınsız bir CPU'da donanım yığını olan bir CPU'dan çok farklı görünecektir). C spec'te yığın çerçevesinin biteceği veya bitmeyeceği hiçbir şey yoktur. Bilmenin tek gerçek yolu, derleyicinizdeki / platformunuzdaki kodu derlemeniz ve ortaya çıkan montajı incelemektir. Derleyicinizin mevcut optimizasyon seçenekleri kümesi de bu konuda büyük rol oynayacaktır.

dKodunuz çalışırken dizinin artık bellek tüketmediğinden emin olmak istiyorsanız, süslü parantez içindeki kodu ayrı bir işleve veya açık bir şekilde mallocve freeotomatik depolama yerine belleğe dönüştürebilirsiniz.


1
"Kıvrık parantezler bir yığın push / pop'a neden olduysa, yukarıdaki kod derlenmeyecektir, çünkü parantezlerin içindeki kod parantezlerin dışındaki değişken değişkene erişemeyecektir" - bu doğru değil. Derleyici her zaman yığın / çerçeve işaretçisinden uzaklığı hatırlayabilir ve dış değişkenlere başvurmak için kullanabilir. Ayrıca, küme parantezleri bir örnek için Joseph'in cevaba bakınız yapmak yığın itme / pop neden olur.
george

@ george- Joseph'in örneğinin yanı sıra tanımladığınız davranış, kullandığınız derleyiciye ve platforma bağlıdır. Örneğin, bir MIPS hedefi için aynı kodu derlemek tamamen farklı sonuçlar verir. Tamamen C spec açısından konuşuyordum (OP bir derleyici veya hedef belirtmediği için). Cevabı düzenleyeceğim ve daha fazla ayrıntı ekleyeceğim.
bta

0

Kapsam dışına çıktığını, ancak işlev dönünceye kadar yığının dışına çıkmadığını düşünüyorum. Bu nedenle, işlev tamamlanana kadar yığın üzerinde bellek alacak, ancak ilk kapanış kıvırcık kümesinin aşağı akışına erişilemeyecek.


3
Garanti yok. Kapsam kapatıldığında, derleyici artık o belleği izlemez (veya en azından ... gerekli değildir) ve yeniden kullanabilir. Bu nedenle daha önce kapsam dışı bir değişken tarafından kullanılan belleğe dokunmak tanımsız davranıştır. Nazal şeytanlara ve benzeri uyarılara dikkat edin.
dmckee --- eski moderatör yavru kedi

0

Standart hakkında gerçekten uygulamaya özel olduğunu gösteren çok fazla bilgi verilmiştir .

Yani, bir deney ilginizi çekebilir. Aşağıdaki kodu denersek:

#include <stdio.h>
int main() {
    int* x;
    int* y;
    {
        int a;
        x = &a;
        printf("%p\n", (void*) x);
    }
    {
        int b;
        y = &b;
        printf("%p\n", (void*) y);
    }
}

Gcc'yi kullanarak burada aynı adresi iki kez elde ederiz: Coliro

Ancak aşağıdaki kodu denersek:

#include <stdio.h>
int main() {
    int* x;
    int* y;
    {
        int a;
        x = &a;
    }
    {
        int b;
        y = &b;
    }
    printf("%p\n", (void*) x);
    printf("%p\n", (void*) y);
}

Gcc'yi kullanarak burada iki farklı adres elde ediyoruz: Coliro

Yani, neler olup bittiğinden gerçekten emin olamazsınız.

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.