Statik değişkenler C ve C ++ 'da nerede saklanır?


180

Yürütülebilir bir dosyanın hangi bölümünde (.BSS, .DATA, diğer) ad çakışması olmaması için statik değişkenler depolanır? Örneğin:


foo.c:                         bar.c:
static int foo = 1;            static int foo = 10;
void fooTest() {               void barTest() {
  static int bar = 2;            static int bar = 20;
  foo++;                         foo++;
  bar++;                         bar++;
  printf("%d,%d", foo, bar);     printf("%d, %d", foo, bar);
}                              }

Ben her iki dosyayı derlemek ve fooTest () ve barTest tekrar tekrar çağıran bir ana bağlarsanız, printf deyimleri bağımsız olarak artar. Foo ve bar değişkenleri çeviri biriminde yerel olduğundan mantıklıdır.

Ancak depolama alanı nereye tahsis edilir?

Açık olmak gerekirse, bir dosyayı ELF biçiminde çıktılayacak bir araç zincirine sahip olduğunuz varsayımıdır. Böylece, ben inanıyorum oradaki vardır biraz boşluk bu statik değişkenler için yürütülebilir dosya olarak ayrılmış olması.
Tartışma amacıyla, GCC araç zincirini kullandığımızı varsayalım.


1
Çoğu kişi, sorunuzu cevaplamak yerine .DATA bölümünde saklanmaları gerektiğini söylüyor: tam olarak .DATA bölümünde nerede ve nasıl nerede bulabilirsiniz. Zaten bir cevabı işaretlediğinizi görüyorum, bu yüzden nasıl bulacağınızı biliyor musunuz?
lukmac

: başlatıldı ve farklı bölümlerde yerleştirilir uninitialised neden linuxjournal.com/article/1059
MHK

1
Çalışma zamanında global / statik değişkenlerinize ayrılan depolama alanının, derleme / bağlantı süresi sırasında gerçekleşen ad çözümlemeleriyle bir ilgisi yoktur. Yürütülebilir dosya oluşturulduktan sonra başka ad kalmadı.
valdo

2
Bu soru anlamsızdır, yanlış sembollerin “isim çarpışmasının” var olmayan bir şey olduğuna dair yanlış dayanak üzerine inşa edilmesi. Meşru bir soru olmaması, bazı cevapların ne kadar korkunç olduğunu açıklayabilir. Çok az insanın buna sahip olduğuna inanmak zor.
underscore_d

Yanıtlar:


131

Statiklerinizin nereye gittiği sıfır sıfırlanıp başlatılmadığına bağlıdır . sıfır başlangıçlı statik veriler girilir .BSS (Sembol Tarafından Başlatılan Blok) , sıfır başlangıç ​​değeri olmayan veriler girilir.


50
"0 olmayan ilklendirilmiş" ile, büyük olasılıkla "ilklendirilmiş, ancak 0 dışında" bir şeyle ifade edersiniz. Çünkü C / C ++ 'da "başlatılmamış" statik veri diye bir şey yoktur. Statik olan her şey varsayılan olarak sıfır başlatılır.
AnT

21
@Don Neufeld: Cevabınız soruya hiç cevap vermiyor. Neden kabul edildiğini anlamıyorum. Çünkü hem 'foo' hem de 'bar' 0 dışıdır. Soru, .bss veya .data
lukmac'a

Açıkça sıfır başlatılan statik verilerin girdiği .datave başlatıcısı olmayan statik verilerin girdiği uygulamaları kullandım .bss.
MM

1
@MM Benim durumumda, statik üyenin başlatılmamış (örtük olarak 0 olarak başlatılmış) veya açıkça 0 olarak başlatılmış olup olmadığı, her iki durumda da .bss bölümüne eklenir.
cbinder

Bu bilgi belirli bir yürütülebilir dosya türüne özgü mü? Belirtmediğiniz için, en azından ELF ve Windows PE yürütülebilir dosyaları için geçerli olduğunu varsayıyorum, ama diğer türler ne olacak?
Jerry Jeremiah

116

Bir program belleğe yüklendiğinde, farklı bölümler halinde düzenlenir. Segmentlerden biri DATA segmentidir . Veri segmenti ayrıca iki bölüme ayrılır:

Başlatılan veri segmenti: Tüm global, statik ve sabit veriler burada saklanır.
Başlatılmamış veri segmenti (BSS): Başlatılmamış tüm veriler bu segmentte saklanır.

İşte bu kavramı açıklayan bir diyagram:

resim açıklamasını buraya girin


İşte bu kavramları açıklayan çok iyi bir bağlantı:

http://www.inf.udec.cl/~leo/teoX.pdf


Yukarıdaki cevap, başlatılan 0'ın BSS'ye gittiğini söylüyor. Başlatılan 0 başlatılmamış mı, yoksa 0 mı? Eğer kendimiz 0 demekse, cevabınıza eklemeniz gerektiğini düşünüyorum.
Viraj

Sabit veriler .data segmentinde değil, metin bölümünün .const segmentinde saklanır.
user10678

Bunun yerine (" Başlatılan veri segmenti : Tüm global, statik ve sabit veriler burada saklanır. Başlatılmamış veri segmenti (BSS) : Başlatılmamış tüm veriler bu segmentte saklanır."), Bunu söylemeliyim: (" Başlatılan veri segmenti : Sıfır olmayan bir değere başlatılan tüm global ve statik değişkenler ve tüm sabit veriler burada saklanır Başlatılmamış veri segmenti (BSS) : Başlatılmamış veya başlatılmamış tüm global ve statik değişkenler sıfıra, bu segmentte saklanır. ").
Gabriel Staples

Ayrıca anladığım kadarıyla "başlatılmış veri" nin başlatılmış değişkenlerden ve sabitlerden oluşabileceğini unutmayın . Bir mikro denetleyicide (ör: STM32), Başlatılan değişkenler varsayılan olarak Flash bellekte depolanır ve başlangıçta RAM'e kopyalanır ve başlatılmış sabitler , yalnızca Flash'ın içinde yer alan metinle birlikte bırakılır ve okunması amaçlanır . yalnızca Flash'ta
Gabriel Staples

Bu diyagramdan topladığım şey, küresel veya statik olan değişkenlerin (statik değişkenler süre içinde küresel değişkenler gibi davrandığı için) ne yığında ne de yığınta olduğu, ancak her ikisinden ayrı olarak bellekte tahsis edildiği. Bu doğru mu? Bellek ayırma daha çok incelemek için yine bir STM32 bağlayıcı komut dosyası bir göz atın sanırım.
Gabriel Staples

32

Aslında, bir değişken tuple (depolama, kapsam, tip, adres, değer):

storage     :   where is it stored, for example data, stack, heap...
scope       :   who can see us, for example global, local...
type        :   what is our type, for example int, int*...
address     :   where are we located
value       :   what is our value

Yerel kapsam, tanımlandığı yere bağlı olarak çeviri birimi (kaynak dosyası), işlev veya blok için yerel anlamına gelebilir. Değişkeni birden fazla işleve görünür kılmak için, kesinlikle DATA ya da BSS alanında olması gerekir (sırasıyla başlatılmasına ya da başlatılmasına bağlı olarak). Daha sonra kaynak dosyadaki tüm işlevlere veya işlevlere göre kapsamlanır.


21

Verilerin depolanma yeri uygulamaya bağlı olacaktır.

Ancak, anlamı statik "iç bağlantı" dir. Böylece sembolüdür derleme birimine (foo.c, bar.c) ve o derleme birimi dışında başvurulan edilemez. Yani isim çarpışması olamaz.


Hayır. statik keyworld'ün aşırı anlamları var: böyle bir durumda statik, depolama değiştiricidir, bağlantı değiştiricidir.
ugasoft

4
ugasoft: fonksiyonun dışındaki statikler bağlantı modifiye edicilerdir, içeride başlangıçta çarpışma olmayacak depolama modifiye edicilerdir.
wnoise

12

"global ve statik" alanda :)

C ++ 'da birkaç bellek alanı vardır:

  • yığın
  • ücretsiz mağaza
  • yığın
  • küresel ve statik
  • const

Sorunuza ayrıntılı bir cevap için buraya bakın :

Aşağıda bir C ++ programının başlıca ayrı bellek alanları özetlenmektedir. Bazı adların (örneğin, "yığın") taslakta [standart] böyle görünmediğini unutmayın.

     Memory Area     Characteristics and Object Lifetimes
     --------------  ------------------------------------------------

     Const Data      The const data area stores string literals and
                     other data whose values are known at compile
                     time.  No objects of class type can exist in
                     this area.  All data in this area is available
                     during the entire lifetime of the program.

                     Further, all of this data is read-only, and the
                     results of trying to modify it are undefined.
                     This is in part because even the underlying
                     storage format is subject to arbitrary
                     optimization by the implementation.  For
                     example, a particular compiler may store string
                     literals in overlapping objects if it wants to.


     Stack           The stack stores automatic variables. Typically
                     allocation is much faster than for dynamic
                     storage (heap or free store) because a memory
                     allocation involves only pointer increment
                     rather than more complex management.  Objects
                     are constructed immediately after memory is
                     allocated and destroyed immediately before
                     memory is deallocated, so there is no
                     opportunity for programmers to directly
                     manipulate allocated but uninitialized stack
                     space (barring willful tampering using explicit
                     dtors and placement new).


     Free Store      The free store is one of the two dynamic memory
                     areas, allocated/freed by new/delete.  Object
                     lifetime can be less than the time the storage
                     is allocated; that is, free store objects can
                     have memory allocated without being immediately
                     initialized, and can be destroyed without the
                     memory being immediately deallocated.  During
                     the period when the storage is allocated but
                     outside the object's lifetime, the storage may
                     be accessed and manipulated through a void* but
                     none of the proto-object's nonstatic members or
                     member functions may be accessed, have their
                     addresses taken, or be otherwise manipulated.


     Heap            The heap is the other dynamic memory area,
                     allocated/freed by malloc/free and their
                     variants.  Note that while the default global
                     new and delete might be implemented in terms of
                     malloc and free by a particular compiler, the
                     heap is not the same as free store and memory
                     allocated in one area cannot be safely
                     deallocated in the other. Memory allocated from
                     the heap can be used for objects of class type
                     by placement-new construction and explicit
                     destruction.  If so used, the notes about free
                     store object lifetime apply similarly here.


     Global/Static   Global or static variables and objects have
                     their storage allocated at program startup, but
                     may not be initialized until after the program
                     has begun executing.  For instance, a static
                     variable in a function is initialized only the
                     first time program execution passes through its
                     definition.  The order of initialization of
                     global variables across translation units is not
                     defined, and special care is needed to manage
                     dependencies between global objects (including
                     class statics).  As always, uninitialized proto-
                     objects' storage may be accessed and manipulated
                     through a void* but no nonstatic members or
                     member functions may be used or referenced
                     outside the object's actual lifetime.

12

Bir çarpışma olacağına inanmıyorum. Dosya düzeyinde statik kullanma (dış işlevler) değişkeni geçerli derleme birimi (dosya) için yerel olarak işaretler. Hiçbir zaman geçerli dosyanın dışında görünmez, bu nedenle harici olarak kullanılabilecek bir ada sahip olmak zorunda değildir.

Bir fonksiyonun içinde statik kullanmak farklıdır - değişken sadece fonksiyon tarafından görülebilir (statik olsun olmasın), sadece bu fonksiyona yapılan çağrılarda değeri korunur.

Aslında, statik nerede olduğuna bağlı olarak iki farklı şey yapar. Ancak her iki durumda da, değişken görünürlük, bağlantı yaparken ad alanı çakışmalarını kolayca önleyebileceğiniz şekilde sınırlandırılmıştır.

Bunu söyledikten sonra, DATAsıfır dışında değerlere başlatılmış değişkenlere sahip olma eğiliminde olan bölümde saklanacağına inanıyorum . Bu, elbette, bir uygulama detayıdır, standart tarafından zorunlu kılınan bir şey değildir - kapakların altında işlerin nasıl yapıldığını değil , sadece davranışı önemsiyor .


1
@paxdiablo: İki tür statik değişkenten bahsettiniz. Bu makaleden hangisine atıfta bulunuluyor ( en.wikipedia.org/wiki/Data_segment )? Veri segmenti aynı zamanda küresel değişkenleri de tutar (bunlar doğada statik olanların tam tersidir). So, how does a segment of memory (Data Segment) store variables that can be accessed from everywhere (global variables) and also those which have limited scope (file scope or function scope in case of static variables)?
Lazer

@ESKay, görünürlükle ilgisi var. Bir derleme biriminde yerel olan bir segmentte depolanan şeyler olabilir, diğerleri tamamen erişilebilir. Bir örnek: VERİ segmentine bir blok katkıda bulunan her bir birimi düşünün. Her şeyin o blokta nerede olduğunu bilir . Ayrıca, diğer bileşenlerin erişmesini istediği blokta bu adresleri de yayınlar. Bağlayıcı bu adresleri bağlantı zamanında çözebilir.
paxdiablo

11

Kendiniz nasıl bulunur? objdump -Sr

Neler olup bittiğini gerçekten anlamak için bağlayıcı yer değiştirmesini anlamalısınız. Buna hiç dokunmadıysanız, önce bu yayını okumayı düşünün .

Kendimizi görmek için bir Linux x86-64 ELF örneğini analiz edelim:

#include <stdio.h>

int f() {
    static int i = 1;
    i++;
    return i;
}

int main() {
    printf("%d\n", f());
    printf("%d\n", f());
    return 0;
}

Şununla derleyin:

gcc -ggdb -c main.c

Kodu şu kodlarla açın:

objdump -Sr main.o
  • -S kodu kaynağın orijinal kaynağı ile ayrıştırır
  • -r yer değiştirme bilgilerini gösterir

İçimizdeki ayrışmayı fgörüyoruz:

 static int i = 1;
 i++;
4:  8b 05 00 00 00 00       mov    0x0(%rip),%eax        # a <f+0xa>
        6: R_X86_64_PC32    .data-0x4

ve segmentin .data-0x4ilk baytına gideceğini söylüyor .data.

-0x4Biz bu nedenle, adresleme RIP akrabası kullanıyor çünkü orada %ripöğretiminde ve R_X86_64_PC32.

RIP'nin 4 bayttan sonra yeniden konumlandırılacak olan aşağıdaki talimatı işaret etmesi gerekir 00 00 00 00. Bunu daha ayrıntılı olarak şu adreste açıkladım: https://stackoverflow.com/a/30515926/895245

Daha sonra, kaynağı i = 1aynı analize değiştirirsek ve aynı analizi yaparsak, şu sonuca varırız:

  • static int i = 0 devam eder .bss
  • static int i = 1 devam eder .data


6

Kullandığınız platforma ve derleyiciye bağlıdır. Bazı derleyiciler doğrudan kod segmentinde saklanır. Statik değişkenlere her zaman yalnızca geçerli çeviri birimi tarafından erişilebilir ve adlar dışa aktarılmaz, bu nedenle ad çakışmalarının hiçbir zaman gerçekleşmemesinin nedeni.


5

Bir derleme biriminde bildirilen veriler, bu dosyaların çıktısı .BSS veya .Data içine gider. BSS'de başlatılan veriler, DATA'da bağımsızleştirildi.

Statik ve global veriler arasındaki fark, dosyaya sembol bilgilerinin dahil edilmesiyle gelir. Derleyiciler sembol bilgilerini ekleme eğilimindedir, ancak yalnızca global bilgileri bu şekilde işaretlerler.

Bağlayıcı bu bilgilere saygı duyar. Statik değişkenler için sembol bilgisi atılır veya karıştırılır, böylece statik değişkenlere yine de bir şekilde başvurulabilir (hata ayıklama veya sembol seçenekleriyle). Her iki durumda da, bağlayıcı ilk olarak yerel referansları çözdüğü için derleme birimleri etkilenmez.


3

Ben objdump ve gdb ile denedim, işte sonuç ne:

(gdb) disas fooTest
Dump of assembler code for function fooTest:
   0x000000000040052d <+0>: push   %rbp
   0x000000000040052e <+1>: mov    %rsp,%rbp
   0x0000000000400531 <+4>: mov    0x200b09(%rip),%eax        # 0x601040 <foo>
   0x0000000000400537 <+10>:    add    $0x1,%eax
   0x000000000040053a <+13>:    mov    %eax,0x200b00(%rip)        # 0x601040 <foo>
   0x0000000000400540 <+19>:    mov    0x200afe(%rip),%eax        # 0x601044 <bar.2180>
   0x0000000000400546 <+25>:    add    $0x1,%eax
   0x0000000000400549 <+28>:    mov    %eax,0x200af5(%rip)        # 0x601044 <bar.2180>
   0x000000000040054f <+34>:    mov    0x200aef(%rip),%edx        # 0x601044 <bar.2180>
   0x0000000000400555 <+40>:    mov    0x200ae5(%rip),%eax        # 0x601040 <foo>
   0x000000000040055b <+46>:    mov    %eax,%esi
   0x000000000040055d <+48>:    mov    $0x400654,%edi
   0x0000000000400562 <+53>:    mov    $0x0,%eax
   0x0000000000400567 <+58>:    callq  0x400410 <printf@plt>
   0x000000000040056c <+63>:    pop    %rbp
   0x000000000040056d <+64>:    retq   
End of assembler dump.

(gdb) disas barTest
Dump of assembler code for function barTest:
   0x000000000040056e <+0>: push   %rbp
   0x000000000040056f <+1>: mov    %rsp,%rbp
   0x0000000000400572 <+4>: mov    0x200ad0(%rip),%eax        # 0x601048 <foo>
   0x0000000000400578 <+10>:    add    $0x1,%eax
   0x000000000040057b <+13>:    mov    %eax,0x200ac7(%rip)        # 0x601048 <foo>
   0x0000000000400581 <+19>:    mov    0x200ac5(%rip),%eax        # 0x60104c <bar.2180>
   0x0000000000400587 <+25>:    add    $0x1,%eax
   0x000000000040058a <+28>:    mov    %eax,0x200abc(%rip)        # 0x60104c <bar.2180>
   0x0000000000400590 <+34>:    mov    0x200ab6(%rip),%edx        # 0x60104c <bar.2180>
   0x0000000000400596 <+40>:    mov    0x200aac(%rip),%eax        # 0x601048 <foo>
   0x000000000040059c <+46>:    mov    %eax,%esi
   0x000000000040059e <+48>:    mov    $0x40065c,%edi
   0x00000000004005a3 <+53>:    mov    $0x0,%eax
   0x00000000004005a8 <+58>:    callq  0x400410 <printf@plt>
   0x00000000004005ad <+63>:    pop    %rbp
   0x00000000004005ae <+64>:    retq   
End of assembler dump.

İşte objdump sonucu

Disassembly of section .data:

0000000000601030 <__data_start>:
    ...

0000000000601038 <__dso_handle>:
    ...

0000000000601040 <foo>:
  601040:   01 00                   add    %eax,(%rax)
    ...

0000000000601044 <bar.2180>:
  601044:   02 00                   add    (%rax),%al
    ...

0000000000601048 <foo>:
  601048:   0a 00                   or     (%rax),%al
    ...

000000000060104c <bar.2180>:
  60104c:   14 00                   adc    $0x0,%al

Yani, dört değişkeniniz veri bölümü olayında aynı adı, ancak farklı ofset ile bulunur.


Bundan çok daha fazlası var. Mevcut cevaplar bile tam değildir. Sadece başka bir şeyden bahsetmek için: yerliler iplik.
Adriano Repetti

2

Daha önce belirtildiği gibi veri segmentinde veya kod segmentinde depolanan statik değişken.
Yığına veya yığına ayrılmayacağından emin olabilirsiniz. Anahtar kelime, değişkenin kapsamını bir dosya veya işlev olarak tanımladığından,
çarpışma riski yoktur, çarpışma staticdurumunda sizi uyarmak için bir derleyici / bağlayıcı vardır.
Güzel bir örnek



1

Cevap çok derleyiciye bağlı olabilir, bu yüzden muhtemelen sorunuzu düzenlemek istersiniz (yani, segment kavramı bile ISO C veya ISO C ++ tarafından zorunlu değildir). Örneğin, Windows'ta bir yürütülebilir dosya sembol adları taşımamaktadır. Bir 'foo' ofset 0x100, diğeri belki 0x2B0 olur ve her iki çeviri biriminin kodu da "onların" foo ofsetleri bilinerek derlenir.


0

her ikisi de bağımsız olarak depolanacak, ancak diğer geliştiricilere açıklık getirmek istiyorsanız, bunları ad alanlarına sarmak isteyebilirsiniz.


-1

Başlatılmamış veri segmenti olarak da adlandırılan bss'de (sembolle başlatmayı engelle) veya başlatılmış veri segmentinde depolandığını zaten biliyorsunuz.

basit bir örnek alalım

void main(void)
{
static int i;
}

yukarıdaki statik değişken başlatılmaz, bu nedenle başlatılmamış veri segmentine (bss) gider.

void main(void)
{
static int i=10;
}

ve elbette 10 ile başlatıldı, böylece başlatılmış veri segmentine gider.

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.