Kapan variabel statis tingkat fungsi dialokasikan / diinisialisasi?

90

Saya cukup yakin bahwa variabel yang dideklarasikan secara global dialokasikan (dan diinisialisasi, jika berlaku) pada waktu mulai program.

int globalgarbage;
unsigned int anumber = 42;

Tapi bagaimana dengan yang statis yang didefinisikan dalam suatu fungsi?

void doSomething()
{
  static bool globalish = true;
  // ...
}

Kapan ruang untuk globalishdialokasikan? Saya menebak kapan program dimulai. Tapi apakah itu juga diinisialisasi? Atau apakah itu diinisialisasi saat doSomething()pertama kali dipanggil?

Owen
sumber

Jawaban:

92

Saya penasaran tentang ini jadi saya menulis program pengujian berikut dan mengkompilasinya dengan g ++ versi 4.1.2.

include <iostream>
#include <string>

using namespace std;

class test
{
public:
        test(const char *name)
                : _name(name)
        {
                cout << _name << " created" << endl;
        }

        ~test()
        {
                cout << _name << " destroyed" << endl;
        }

        string _name;
};

test t("global variable");

void f()
{
        static test t("static variable");

        test t2("Local variable");

        cout << "Function executed" << endl;
}


int main()
{
        test t("local to main");

        cout << "Program start" << endl;

        f();

        cout << "Program end" << endl;
        return 0;
}

Hasilnya tidak seperti yang saya harapkan. Konstruktor untuk objek statis tidak dipanggil sampai fungsi tersebut pertama kali dipanggil. Ini hasilnya:

global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
Adam Pierce
sumber
30
Sebagai klarifikasi: variabel statis diinisialisasi saat eksekusi pertama kali mencapai deklarasinya, bukan saat fungsi penampung dipanggil. Jika Anda hanya memiliki statis di awal fungsi (misalnya dalam contoh Anda) ini sama, tetapi belum tentu: misalnya jika Anda memiliki 'if (...) {static MyClass x; ...} ', maka' x 'tidak akan diinisialisasi sama sekali selama eksekusi pertama fungsi itu dalam kasus di mana kondisi pernyataan if bernilai false.
EvanED
4
Tetapi bukankah hal ini menyebabkan overhead runtime, karena setiap kali variabel statis digunakan, program harus memeriksa apakah variabel tersebut telah digunakan sebelumnya, karena jika tidak, ia harus diinisialisasi? Dalam hal itu, hal itu sedikit menyebalkan.
HelloGoodbye
ilustrasi sempurna
Des1gnWizard
@veio: Ya, inisialisasi aman untuk thread. Lihat pertanyaan itu untuk lebih jelasnya: stackoverflow.com/questions/23829389/…
Rémi
2
@HelloGoodbye: ya, ini mengarah ke overhead runtime. Juga lihat pertanyaan itu: stackoverflow.com/questions/23829389/…
Rémi
53

Beberapa verbiage relevan dari C ++ Standard:

3.6.2 Inisialisasi objek non-lokal [basic.start.init]

1

Penyimpanan objek dengan durasi penyimpanan statis ( basic.stc.static ) harus diinisialisasi nol ( dcl.init ) sebelum inisialisasi lainnya terjadi. Objek jenis POD ( basic.types ) dengan durasi penyimpanan statis yang diinisialisasi dengan ekspresi konstan ( expr.const ) harus diinisialisasi sebelum inisialisasi dinamis terjadi. Objek ruang lingkup namespace dengan durasi penyimpanan statis yang ditentukan dalam unit terjemahan yang sama dan diinisialisasi secara dinamis harus diinisialisasi dalam urutan definisi mereka muncul di unit terjemahan. [Catatan: dcl.init.aggr menjelaskan urutan di mana anggota agregat diinisialisasi. Inisialisasi objek statis lokal dijelaskan di stmt.dcl . ]

[lebih banyak teks di bawah ini menambahkan lebih banyak kebebasan untuk penulis kompiler]

6.7 Pernyataan pernyataan [stmt.dcl]

...

4

Inisialisasi nol ( dcl.init ) dari semua objek lokal dengan durasi penyimpanan statis ( basic.stc.static ) dilakukan sebelum inisialisasi lainnya terjadi. Objek lokal jenis POD ( basic.types ) dengan durasi penyimpanan statis yang diinisialisasi dengan ekspresi konstan diinisialisasi sebelum bloknya pertama kali dimasukkan. Implementasi diizinkan untuk melakukan inisialisasi awal objek lokal lainnya dengan durasi penyimpanan statis dalam kondisi yang sama dengan implementasi diizinkan untuk menginisialisasi objek secara statis dengan durasi penyimpanan statis dalam cakupan namespace ( basic.start.init). Jika tidak, objek seperti itu diinisialisasi saat kontrol pertama kali melewati deklarasinya; objek seperti itu dianggap dimulai setelah selesainya inisialisasi. Jika inisialisasi keluar dengan melontarkan pengecualian, inisialisasi tidak selesai, sehingga akan dicoba lagi saat kontrol memasuki deklarasi berikutnya. Jika kontrol masuk kembali ke deklarasi (secara rekursif) saat objek sedang diinisialisasi, perilaku tidak terdefinisi. [ Contoh:

      int foo(int i)
      {
          static int s = foo(2*i);  // recursive call - undefined
          return i+1;
      }

- contoh akhir ]

5

Destruktor untuk objek lokal dengan durasi penyimpanan statis akan dijalankan jika dan hanya jika variabel dibuat. [Catatan: basic.start.term menjelaskan urutan pemusnahan objek lokal dengan durasi penyimpanan statis. ]

Jason Plank
sumber
Ini menjawab pertanyaan saya dan tidak bergantung pada "bukti anekdot" tidak seperti jawaban yang diterima. Saya secara khusus mencari penyebutan pengecualian ini dalam konstruktor objek statis lokal fungsi yang diinisialisasi secara statis:If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
Bensge
26

Memori untuk semua variabel statis dialokasikan pada pemuatan program. Tetapi variabel statis lokal dibuat dan diinisialisasi saat pertama kali digunakan, bukan saat program dijalankan. Ada beberapa bacaan bagus tentang itu, dan statika secara umum, di sini . Secara umum saya pikir beberapa masalah ini bergantung pada implementasinya, terutama jika Anda ingin tahu di mana di memori barang ini akan ditempatkan.

Eugene
sumber
2
kurang tepat, statika lokal dialokasikan dan diinisialisasi nol "pada pemuatan program" (dalam tanda kutip, karena itu juga kurang tepat), lalu diinisialisasi ulang saat pertama kali fungsi tempat mereka dimasukkan.
Mooing Duck
Sepertinya tautan itu sekarang putus, 7 tahun kemudian.
Steve
1
Ya, tautan putus. Ini arsipnya: web.archive.org/web/20100328062506/http://www.acm.org/…
Eugene
10

Kompilator akan mengalokasikan variabel statis yang ditentukan dalam suatu fungsi foopada saat pemuatan program, namun kompilator juga akan menambahkan beberapa instruksi tambahan (kode mesin) ke fungsi Anda foosehingga saat pertama kali dipanggil, kode tambahan ini akan menginisialisasi variabel statis ( misalnya memanggil konstruktor, jika ada).

@Adam: Ini di belakang layar injeksi kode oleh kompilator adalah alasan untuk hasil yang Anda lihat.

Henk
sumber
5

Saya mencoba menguji lagi kode dari Adam Pierce dan menambahkan dua kasus lagi: variabel statis di kelas dan tipe POD. Kompiler saya adalah g ++ 4.8.1, di OS Windows (MinGW-32). Hasilnya adalah variabel statis di kelas diperlakukan sama dengan variabel global. Konstruktornya akan dipanggil sebelum masuk ke fungsi utama.

  • Kesimpulan (untuk g ++, lingkungan Windows):

    1. Variabel global dan anggota statis di kelas : konstruktor dipanggil sebelum masuk ke fungsi utama (1) .
    2. Variabel statis lokal : konstruktor hanya dipanggil ketika eksekusi mencapai deklarasinya pada pertama kali.
    3. Jika variabel statis lokal berjenis POD , maka ia juga diinisialisasi sebelum masuk ke fungsi utama (1) . Contoh tipe POD: static int number = 10;

(1) : Status yang benar harus: "sebelum fungsi apa pun dari unit terjemahan yang sama dipanggil". Namun untuk sederhananya seperti contoh di bawah ini, maka itu adalah fungsi utama .

sertakan <iostream>

#include < string>

using namespace std;

class test
{
public:
   test(const char *name)
            : _name(name)
    {
            cout << _name << " created" << endl;
    }

    ~test()
    {
            cout << _name << " destroyed" << endl;
    }

    string _name;
    static test t; // static member
 };
test test::t("static in class");

test t("global variable");

void f()
{
    static  test t("static variable");
    static int num = 10 ; // POD type, init before enter main function

    test t2("Local variable");
    cout << "Function executed" << endl;
}

int main()
{
    test t("local to main");
    cout << "Program start" << endl;
    f();
    cout << "Program end" << endl;
    return 0;
 }

hasil:

static in class created
global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
static in class destroyed

Ada yang diuji di Linux env?

Thang Le
sumber
4

Atau apakah itu diinisialisasi ketika doSomething () pertama kali dipanggil?

Ya itu. Ini, antara lain, memungkinkan Anda menginisialisasi struktur data yang diakses secara global jika diperlukan, misalnya di dalam blok coba / tangkap. Misalnya, bukan

int foo = init(); // bad if init() throws something

int main() {
  try {
    ...
  }
  catch(...){
    ...
  }
}

kamu bisa menulis

int& foo() {
  static int myfoo = init();
  return myfoo;
}

dan gunakan di dalam blok coba / tangkap. Pada panggilan pertama, variabel akan diinisialisasi. Kemudian, pada panggilan pertama dan berikutnya, nilainya akan dikembalikan (dengan referensi).

dmityugov.dll
sumber
3

Variabel statis dialokasikan di dalam segmen kode - mereka adalah bagian dari gambar yang dapat dieksekusi, dan begitu juga dipetakan di yang sudah diinisialisasi.

Variabel statis dalam ruang lingkup fungsi diperlakukan sama, pelingkupannya murni konstruksi tingkat bahasa.

Untuk alasan ini Anda dijamin bahwa variabel statis akan diinisialisasi ke 0 (kecuali Anda menentukan sesuatu yang lain) daripada nilai yang tidak ditentukan.

Ada beberapa aspek lain untuk inisialisasi yang dapat Anda manfaatkan - misalnya, segmen bersama memungkinkan berbagai contoh yang dapat dieksekusi berjalan sekaligus untuk mengakses variabel statis yang sama.

Dalam C ++ (cakupan global), objek statis memiliki konstruktornya yang disebut sebagai bagian dari program yang dimulai, di bawah kendali pustaka runtime C. Di bawah Visual C ++ setidaknya urutan objek yang diinisialisasi dapat dikontrol oleh pragma init_seg .

Rob Walker
sumber
4
Pertanyaan ini tentang statika bercakupan fungsi. Setidaknya ketika mereka memiliki konstruktor nontrivial mereka diinisialisasi pada entri pertama ke dalam fungsi. Atau lebih khusus lagi, saat garis itu tercapai.
Adam Mitz
Benar - tetapi pertanyaannya berbicara tentang ruang yang dialokasikan ke variabel, dan menggunakan tipe data sederhana. Ruang masih dialokasikan di segmen kode
Rob Walker
Saya tidak melihat bagaimana segmen kode vs. segmen data sangat penting di sini. Saya pikir kami membutuhkan klarifikasi dari OP. Dia mengatakan "dan menginisialisasi, jika ada".
Adam Mitz
5
variabel tidak pernah dialokasikan di dalam segmen kode; dengan cara ini mereka tidak akan bisa menulis.
botismarius
1
variabel statis dialokasikan ruang di segmen data atau di segmen bss tergantung pada apakah mereka diinisialisasi atau tidak.
EmptyData