Di mana variabel statis disimpan dalam C dan C ++?

180

Dalam segmen apa (.BSS, .DATA, lainnya) dari file yang dapat dieksekusi adalah variabel statis yang disimpan sehingga mereka tidak memiliki nama tabrakan? Sebagai contoh:


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);
}                              }

Jika saya mengkompilasi kedua file dan menautkannya ke utama yang memanggil fooTest () dan barTest berulang kali, pernyataan printf bertambah secara independen. Masuk akal karena variabel foo dan bar lokal untuk unit terjemahan.

Tetapi di mana penyimpanan dialokasikan?

Agar lebih jelas, asumsinya adalah Anda memiliki toolchain yang akan menampilkan file dalam format ELF. Jadi, saya percaya bahwa ada memiliki untuk beberapa ruang dicadangkan dalam file executable untuk variabel-variabel statis.
Untuk tujuan diskusi, mari kita asumsikan kita menggunakan GCC toolchain.

Benoit
sumber
1
Kebanyakan orang memberi tahu Anda bahwa mereka harus disimpan di bagian .DATA alih-alih menjawab pertanyaan Anda: di mana tepatnya di bagian .DATA dan bagaimana Anda bisa menemukan di mana. Saya melihat Anda sudah menandai jawaban, jadi Anda sudah tahu cara menemukannya?
lukmac
mengapa diinisialisasi dan tidak diinisialisasi ditempatkan di bagian yang berbeda: linuxjournal.com/article/1059
mhk
1
Penyimpanan yang dialokasikan untuk variabel global / statis Anda saat runtime tidak ada hubungannya dengan resolusi nama mereka, yang terjadi selama waktu pembuatan / tautan. Setelah executable dibangun - tidak ada nama lagi.
valdo
2
Pertanyaan ini tidak ada artinya, dibangun di atas premis yang salah bahwa "tabrakan nama" dari simbol-simbol yang tidak diekspor adalah sesuatu yang dapat ada. Fakta bahwa tidak ada pertanyaan yang sah dapat menjelaskan betapa mengerikannya beberapa jawaban. Sulit dipercaya begitu sedikit orang yang mendapatkannya.
underscore_d

Jawaban:

131

Ke mana statika Anda bergantung pada apakah statika tersebut diinisialisasi nol . data statis nol-diinisialisasi masuk .BSS (Blok Dimulai oleh Simbol) , data non-nol-diinisialisasi masuk .DATA

Don Neufeld
sumber
50
Dengan "non-0 diinisialisasi" Anda mungkin berarti "diinisialisasi, tetapi dengan sesuatu selain 0". Karena tidak ada yang namanya data statis "tidak diinisialisasi" dalam C / C ++. Semuanya statis diinisialisasi-nol secara default.
AnT
21
@ Don Neufeld: jawaban Anda tidak menjawab pertanyaan sama sekali. Saya tidak mengerti mengapa itu diterima. Karena 'foo' dan 'bar' keduanya tidak diinisialisasi. Pertanyaannya adalah di mana harus menempatkan dua variabel statis / global dengan nama yang sama di .bss atau
.data
Saya telah menggunakan implementasi di mana data statis yang secara eksplisit diinisialisasi nol masuk .data, dan data statis tanpa inisialisasi masuk .bss.
MM
1
@ MM Dalam kasus saya apakah anggota statis tidak diinisialisasi (secara implisit diinisialisasi ke 0) atau secara eksplisit diinisialisasi ke 0, dalam kedua kasus itu ditambahkan di bagian .bss.
cbinder
Apakah info ini khusus untuk jenis file yang dapat dieksekusi tertentu? Saya berasumsi, karena Anda tidak menentukan, bahwa itu berlaku setidaknya untuk file executable ELF dan Windows PE, tetapi bagaimana dengan tipe lainnya?
Jerry Jeremiah
116

Ketika sebuah program dimuat ke dalam memori, itu disusun dalam segmen yang berbeda. Salah satu segmen adalah segmen DATA . Segmen Data selanjutnya dibagi menjadi dua bagian:

Segmen data yang diinisialisasi: Semua data global, statis, dan konstan disimpan di sini.
Segmen data yang tidak diinisialisasi (BSS): Semua data yang tidak diinisialisasi disimpan di segmen ini.

Berikut ini diagram untuk menjelaskan konsep ini:

masukkan deskripsi gambar di sini


di sini ada tautan yang sangat bagus untuk menjelaskan konsep-konsep ini:

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

karn
sumber
Jawaban di atas mengatakan 0 diinisialisasi masuk ke BSS. Apakah 0 yang diinisialisasi berarti tidak diinisialisasi atau 0 per se? Jika itu berarti 0 per se maka saya pikir Anda harus memasukkannya dalam jawaban Anda.
Viraj
Data konstan tidak disimpan dalam segmen data. Tetapi dalam segmen .const dari bagian teks.
user10678
Alih-alih ini (" Segmen data yang diinisialisasi : Semua data global, statis, dan konstan disimpan di sini. Segmen data yang tidak diinisialisasi (BSS) : Semua data yang tidak diinisialisasi disimpan dalam segmen ini."), Saya pikir seharusnya mengatakan ini: (" Segmen data yang diinisialisasi: Semua variabel global & statis yang diinisialisasi ke nilai non-nol, dan semua data konstan, disimpan di sini. Segmen data yang tidak diinisialisasi (BSS) : Semua variabel global dan statis yang TIDAK diinisialisasi, atau diinisialisasi ke nol, disimpan di segmen ini. ").
Gabriel Staples
Juga perhatikan bahwa sejauh yang saya mengerti, "data yang diinisialisasi" dapat terdiri dari variabel dan konstanta yang diinisialisasi . Pada mikrokontroler (mis: STM32), variabel yang diinisialisasi disimpan secara default di memori Flash dan disalin ke RAM saat startup , dan konstanta yang diinisialisasi akan ditinggalkan, dan dimaksudkan untuk dibaca dari, hanya Flash , bersama dengan teks , yang berisi program itu sendiri, dan hanya tersisa di Flash.
Gabriel Staples
Jadi yang saya kumpulkan dari diagram ini adalah bahwa variabel yang bersifat global atau statis (karena variabel statis bertindak seperti variabel global dalam durasi) tidak ada di heap atau stack, tetapi lebih dialokasikan pada memori selain dari keduanya. Apakah itu benar? Saya kira saya bisa melihat skrip linker STM32 lagi untuk mempelajari alokasi memori juga.
Gabriel Staples
32

Faktanya, sebuah variabel adalah tuple (penyimpanan, ruang lingkup, jenis, alamat, nilai):

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

Lingkup lokal dapat berarti lokal untuk unit translasi (file sumber), fungsi atau blok tergantung di mana didefinisikan. Untuk membuat variabel terlihat lebih dari satu fungsi, itu pasti harus dalam DATA atau area BSS (tergantung pada apakah masing-masing diinisialisasi secara eksplisit atau tidak). Kemudian dicakup sesuai dengan semua fungsi atau fungsi dalam file sumber.

yogeesh
sumber
21

Lokasi penyimpanan data akan tergantung pada implementasi.

Namun, makna statis adalah "hubungan internal". Dengan demikian, simbol internal ke unit kompilasi (foo.c, bar.c) dan tidak dapat dirujuk di luar unit kompilasi. Jadi, tidak ada tabrakan nama.

Seb Rose
sumber
tidak. static keyworld memiliki arti berlebihan: dalam kasus seperti itu statis adalah pengubah penyimpanan, bukan pengubah hubungan.
ugasoft
4
ugasoft: statika di luar fungsi adalah pengubah hubungan, di dalamnya ada pengubah penyimpanan di mana tidak ada tabrakan untuk memulai.
Berkumandang
12

di area "global dan statis" :)

Ada beberapa area memori di C ++:

  • tumpukan
  • toko gratis
  • tumpukan
  • global & statis
  • const

Lihat di sini untuk jawaban terperinci untuk pertanyaan Anda:

Berikut ini ringkasan area memori utama program C ++. Perhatikan bahwa beberapa nama (misalnya, "tumpukan") tidak muncul seperti itu dalam konsep [standar].

     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.
ugasoft
sumber
12

Saya tidak percaya akan ada tabrakan. Menggunakan statis di tingkat file (fungsi luar) menandai variabel sebagai lokal ke unit kompilasi saat ini (file). Itu tidak pernah terlihat di luar file saat ini sehingga tidak pernah harus memiliki nama yang dapat digunakan secara eksternal.

Menggunakan statis di dalam suatu fungsi berbeda - variabel hanya terlihat oleh fungsi (apakah statis atau tidak), hanya saja nilainya dipertahankan di seluruh panggilan ke fungsi tersebut.

Akibatnya, statis melakukan dua hal berbeda tergantung di mana tempatnya. Namun dalam kedua kasus, visibilitas variabel terbatas sedemikian rupa sehingga Anda dapat dengan mudah mencegah bentrokan namespace saat menautkan.

Karena itu, saya percaya itu akan disimpan di DATAbagian, yang cenderung memiliki variabel yang diinisialisasi ke nilai selain nol. Ini, tentu saja, detail implementasi, bukan sesuatu yang diamanatkan oleh standar - itu hanya peduli tentang perilaku, bukan bagaimana hal-hal dilakukan di bawah selimut.

paxdiablo
sumber
1
@paxdiablo: Anda telah menyebutkan dua jenis variabel statis. Yang mana dari yang dimaksud artikel ini ( en.wikipedia.org/wiki/Data_segment )? Segmen data juga memiliki variabel global (yang bertolak belakang dengan variabel statis). 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
@ EKKay, itu harus dilakukan dengan visibilitas. Mungkin ada hal-hal yang disimpan dalam segmen yang bersifat lokal ke unit kompilasi, yang lain dapat diakses sepenuhnya. Satu contoh: pikirkan setiap unit yang menyumbang blok ke segmen DATA. Ia tahu di mana semuanya berada di blok itu. Itu juga menerbitkan alamat dari hal-hal di blok yang ia ingin unit lain untuk memiliki akses. Tautan dapat menyelesaikan alamat tersebut pada waktu tautan.
paxdiablo
11

Cara menemukannya sendiri objdump -Sr

Untuk benar-benar memahami apa yang sedang terjadi, Anda harus memahami relokasi linker. Jika Anda belum pernah menyentuhnya, pertimbangkan untuk membaca posting ini terlebih dahulu .

Mari kita menganalisis contoh ELF Linux x86-64 untuk melihatnya sendiri:

#include <stdio.h>

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

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

Kompilasi dengan:

gcc -ggdb -c main.c

Dekompilasi kode dengan:

objdump -Sr main.o
  • -S mendekompilasi kode dengan sumber asli yang disatukan
  • -r menunjukkan informasi relokasi

Di dalam dekompilasi fkita melihat:

 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

dan .data-0x4mengatakan bahwa itu akan pergi ke byte pertama dari .datasegmen tersebut.

Ada di -0x4sana karena kita menggunakan pengalamatan RIP relatif, sehingga %ripdalam instruksi dan R_X86_64_PC32.

Ini diperlukan karena RIP menunjuk ke instruksi berikut , yang dimulai 4 byte dan setelah 00 00 00 00itu apa yang akan dipindahkan. Saya telah menjelaskan ini secara lebih rinci di: https://stackoverflow.com/a/30515926/895245

Kemudian, jika kita memodifikasi sumber ke i = 1dan melakukan analisis yang sama, kita menyimpulkan bahwa:

  • static int i = 0 terus .bss
  • static int i = 1 terus .data
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
7

Ini caranya (mudah dimengerti):

tumpukan, tumpukan dan data statis

Yousha Aleayoub
sumber
6

Itu tergantung pada platform dan kompiler yang Anda gunakan. Beberapa kompiler menyimpan langsung di segmen kode. Variabel statis selalu hanya dapat diakses oleh unit terjemahan saat ini dan nama tidak diekspor sehingga alasan tabrakan nama tidak pernah terjadi.

trotterdylan
sumber
5

Data yang dideklarasikan di unit kompilasi akan masuk ke .BSS atau .Data dari output file itu. Data diinisialisasi dalam BSS, tidak diinisialisasi dalam DATA.

Perbedaan antara data statis dan global datang dalam penyertaan informasi simbol dalam file. Compiler cenderung memasukkan informasi simbol tetapi hanya menandai informasi global seperti itu.

Tautan menghormati informasi ini. Informasi simbol untuk variabel statis dibuang atau dihancurkan sehingga variabel statis masih dapat dirujuk dengan beberapa cara (dengan opsi debug atau simbol). Dalam kedua kasus ini, unit kompilasi tidak akan terpengaruh ketika tautan menyelesaikan referensi lokal terlebih dahulu.

itj
sumber
3

Saya mencobanya dengan objdump dan gdb, berikut ini hasilnya yang saya dapat:

(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.

di sini adalah hasil objdump

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

Jadi, bisa dikatakan, keempat variabel Anda berada di acara bagian data dengan nama yang sama, tetapi dengan offset yang berbeda.

Dan
sumber
Ada jauh lebih banyak dari itu. Bahkan jawaban yang ada tidak lengkap. Hanya untuk menyebutkan sesuatu yang lain: utas penduduk setempat.
Adriano Repetti
2

variabel statis disimpan dalam segmen data atau segmen kode seperti yang disebutkan sebelumnya.
Anda dapat yakin bahwa itu tidak akan dialokasikan pada tumpukan atau tumpukan.
Tidak ada risiko tabrakan karena statickata kunci menentukan ruang lingkup variabel untuk menjadi file atau fungsi, dalam kasus tabrakan ada kompiler / penghubung untuk memperingatkan Anda tentang.
A nice contoh

Ilya
sumber
1

Jawabannya mungkin sangat tergantung pada kompiler, jadi Anda mungkin ingin mengedit pertanyaan Anda (maksud saya, bahkan gagasan segmen tidak diamanatkan oleh ISO C atau ISO C ++). Misalnya, pada Windows yang dapat dieksekusi tidak membawa nama simbol. Satu 'foo' akan diimbangi 0x100, yang lainnya mungkin 0x2B0, dan kode dari kedua unit terjemahan dikompilasi dengan mengetahui offset untuk foo "mereka".

MSalters
sumber
0

keduanya akan disimpan secara independen, namun jika Anda ingin menjelaskannya kepada pengembang lain, Anda mungkin ingin membungkusnya dalam ruang nama.

Robert Gould
sumber
-1

Anda sudah tahu apakah itu menyimpan dalam bss (blok mulai dengan simbol) juga disebut sebagai segmen data yang tidak diinisialisasi atau di segmen data yang diinisialisasi.

mari kita ambil contoh sederhana

void main(void)
{
static int i;
}

variabel statis di atas tidak diinisialisasi, jadi pergi ke segmen data yang tidak diinisialisasi (bss).

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

dan tentu saja diinisialisasi oleh 10 sehingga masuk ke segmen data yang diinisialisasi.

Anurag Bhakuni
sumber