Berapakah umur variabel statis dalam fungsi C ++?

373

Jika suatu variabel dinyatakan sebagai staticdalam lingkup fungsi, ia hanya diinisialisasi satu kali dan mempertahankan nilainya di antara panggilan fungsi. Apa sebenarnya masa hidupnya? Kapan konstruktor dan destruktornya dipanggil?

void foo() 
{ 
    static string plonk = "When will I die?";
}
Motti
sumber

Jawaban:

257

staticVariabel fungsi seumur hidup dimulai pertama kali [0] aliran program menemui deklarasi dan berakhir pada penghentian program. Ini berarti bahwa run-time harus melakukan pembukuan untuk menghancurkannya hanya jika itu benar-benar dibangun.

Selain itu, karena standar mengatakan bahwa penghancur objek statis harus berjalan dalam urutan terbalik dari penyelesaian konstruksi mereka [1] , dan urutan konstruksi dapat bergantung pada program spesifik yang dijalankan, urutan konstruksi harus diperhitungkan .

Contoh

struct emitter {
    string str;
    emitter(const string& s) : str(s) { cout << "Created " << str << endl; }
    ~emitter() { cout << "Destroyed " << str << endl; }
};

void foo(bool skip_first) 
{
    if (!skip_first)
        static emitter a("in if");
    static emitter b("in foo");
}

int main(int argc, char*[])
{
    foo(argc != 2);
    if (argc == 3)
        foo(false);
}

Keluaran:

C:> sample.exe
Dibuat di foo
Hancur di foo

C:> sample.exe 1
Dibuat jika
Dibuat di foo
Hancur di foo
Hancur di jika

C:> sample.exe 1 2
Dibuat di foo
Diciptakan jika
Dihancurkan jika
Dihancurkan di foo

[0]Karena C ++ 98 [2] tidak memiliki referensi ke banyak utas bagaimana ini akan berperilaku dalam lingkungan multi-utas tidak ditentukan, dan dapat menjadi masalah seperti yang disebutkan Roddy .

[1] C ++ 98 bagian 3.6.3.1 [basic.start.term]

[2]Dalam statika C ++ 11 diinisialisasi dengan cara yang aman, ini juga dikenal sebagai Statika Sihir .

Motti
sumber
2
Untuk tipe sederhana tanpa efek samping c'tor / d'tor, ini merupakan pengoptimalan langsung untuk menginisialisasi mereka dengan cara yang sama seperti tipe sederhana global. Ini menghindari masalah percabangan, bendera, dan urutan kehancuran. Itu tidak berarti seumur hidup mereka berbeda.
John McFarlane
1
Jika fungsi dapat dipanggil oleh banyak utas, lalu apakah ini berarti Anda harus memastikan bahwa deklarasi statis harus dilindungi oleh mutex di C ++ 98 ??
allyourcode
1
"destruktor 'dari objek global harus berjalan dalam urutan terbalik dari penyelesaian konstruksi mereka" tidak berlaku di sini, karena objek ini tidak global. Urutan penghancuran penduduk setempat dengan durasi penyimpanan statis atau ulir jauh lebih rumit daripada LIFO murni, lihat bagian 3.6.3[basic.start.term]
Ben Voigt
2
Ungkapan "saat penghentian program" tidak sepenuhnya benar. Bagaimana dengan statika di Windows dll yang dimuat dan dibongkar secara dinamis? Jelas standar C ++ tidak berurusan dengan majelis sama sekali (akan lebih baik jika itu dilakukan), tetapi klarifikasi tentang apa yang dikatakan standar di sini akan baik. Jika frasa "saat penghentian program" dimasukkan, maka secara teknis akan membuat implementasi C ++ dengan majelis yang dibongkar secara dinamis tidak sesuai.
Roger Sanders
2
@Motti Saya tidak percaya standar secara eksplisit memungkinkan pustaka dinamis, tetapi sampai sekarang saya juga tidak percaya ada sesuatu yang spesifik dalam standar yang bertentangan dengan implementasinya. Tentu saja, secara tegas bahasa di sini tidak menyatakan bahwa objek statis tidak dapat dihancurkan lebih awal melalui cara lain, hanya saja mereka harus dihancurkan ketika kembali dari main atau memanggil std :: exit. Garis yang cukup bagus menurut saya.
Roger Sanders
125

Motti benar tentang pesanan, tetapi ada beberapa hal yang perlu dipertimbangkan:

Compiler biasanya menggunakan variabel flag tersembunyi untuk menunjukkan apakah statika lokal telah diinisialisasi, dan flag ini diperiksa pada setiap entri ke fungsi. Jelas ini adalah hit kinerja kecil, tetapi yang lebih memprihatinkan adalah bahwa flag ini tidak dijamin aman.

Jika Anda memiliki statis lokal seperti di atas, dan foodipanggil dari banyak utas, Anda mungkin memiliki kondisi balapan yang menyebabkan plonkinisialisasi salah atau bahkan beberapa kali. Juga, dalam hal ini plonkdapat dihancurkan oleh utas berbeda dari yang membangunnya.

Terlepas dari apa yang dikatakan standar, saya akan sangat waspada terhadap urutan sebenarnya dari penghancuran statis lokal, karena mungkin Anda tanpa disadari bergantung pada statis yang masih berlaku setelah dihancurkan, dan ini benar-benar sulit untuk dilacak.

Roddy
sumber
68
C ++ 0x mengharuskan inisialisasi statis aman dari utas. Jadi berhati-hatilah tetapi segalanya hanya akan menjadi lebih baik.
deft_code
Masalah pesanan kehancuran dapat dihindari dengan sedikit kebijakan. objek statis / global (lajang, dll) tidak boleh mengakses objek statis lainnya di badan metode mereka. Mereka hanya akan diakses di konstruktor di mana referensi / pointer dapat disimpan untuk akses nanti dalam metode. Ini tidak sempurna tetapi harus memperbaiki 99 dari kasus dan kasus yang tidak ditangkap jelas mencurigakan dan harus ditangkap dalam tinjauan kode. Ini masih bukan perbaikan yang sempurna karena kebijakan tidak dapat diberlakukan dalam bahasa
deft_code
Saya sedikit noob, tetapi mengapa kebijakan ini tidak dapat diberlakukan dalam bahasa ini?
cjcurrie
9
Sejak C ++ 11, ini tidak lagi menjadi masalah. Jawaban Motti diperbarui sesuai dengan itu.
Nilanjan Basu
10

Penjelasan yang ada tidak benar-benar lengkap tanpa aturan aktual dari Standar, ditemukan pada 6.7:

Inisialisasi nol dari semua variabel blok-lingkup dengan durasi penyimpanan statis atau durasi penyimpanan utas dilakukan sebelum inisialisasi lainnya dilakukan. Inisialisasi konstan entitas blok-lingkup dengan durasi penyimpanan statis, jika berlaku, dilakukan sebelum bloknya pertama kali dimasukkan. Implementasi diizinkan untuk melakukan inisialisasi awal variabel blok-lingkup lain dengan durasi penyimpanan statis atau thread dalam kondisi yang sama bahwa implementasi diizinkan untuk menginisialisasi variabel secara statis dengan durasi penyimpanan statis atau thread dalam lingkup namespace. Kalau tidak, variabel tersebut diinisialisasi ketika kontrol pertama kali melewati deklarasi; variabel semacam itu dianggap diinisialisasi setelah selesainya inisialisasi. Jika inisialisasi keluar dengan melemparkan pengecualian, inisialisasi tidak lengkap, sehingga akan dicoba lagi saat kontrol berikutnya memasuki deklarasi. Jika kontrol memasuki deklarasi bersamaan saat variabel diinisialisasi, eksekusi bersamaan harus menunggu selesainya inisialisasi. Jika kontrol memasukkan kembali deklarasi secara berulang ketika variabel diinisialisasi, perilaku tidak terdefinisi.

Ben Voigt
sumber
8

FWIW, Codegear C ++ Builder tidak merusak dalam urutan yang diharapkan sesuai dengan standar.

C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if

... yang merupakan alasan lain untuk tidak bergantung pada perintah penghancuran!

Roddy
sumber
57
Bukan argumen yang bagus. Saya akan mengatakan ini lebih merupakan argumen untuk tidak menggunakan kompiler ini.
Martin York
26
Hmm. Jika Anda tertarik untuk menghasilkan kode portabel dunia nyata, bukan hanya kode portabel secara teoritis, saya pikir itu berguna untuk mengetahui area bahasa apa yang dapat menyebabkan masalah. Saya akan terkejut jika C ++ Builder unik karena tidak menangani ini.
Roddy
17
Saya setuju, kecuali bahwa saya akan menyebutnya sebagai "apa yang menyebabkan masalah pada kompiler, dan bidang bahasa apa yang mereka gunakan" ;-P
Steve Jessop
0

The Variabel statis yang ikut bermain setelah dimulai pelaksanaan program dan tetap tersedia sampai ujung eksekusi program.

Variabel statis dibuat di Segmen Data Memori .

Chandra Shekhar
sumber
ini tidak benar untuk variabel pada lingkup fungsi
awerries