Deklarasi variabel dalam file header - statis atau tidak?

91

Ketika melakukan refactoring beberapa #definessaya menemukan deklarasi yang mirip dengan berikut ini di file header C ++:

static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;

Pertanyaannya adalah, apa bedanya, jika ada, yang akan dihasilkan oleh statis? Perhatikan bahwa beberapa penyertaan header tidak dimungkinkan karena #ifndef HEADER #define HEADER #endiftrik klasik (jika itu penting).

Apakah statik berarti hanya satu salinan VALyang dibuat, jika header disertakan oleh lebih dari satu file sumber?

rampok
sumber
related: stackoverflow.com/questions/177437/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

107

The staticberarti bahwa akan ada satu salinan dari VALdibuat untuk setiap file sumber itu termasuk dalam. Tetapi juga berarti bahwa beberapa inklusi tidak akan menghasilkan beberapa definisi VALyang akan bertabrakan pada link waktu. Di C, tanpa staticAnda perlu memastikan bahwa hanya satu file sumber yang ditentukan VALsedangkan file sumber lainnya mendeklarasikannya extern. Biasanya seseorang akan melakukan ini dengan mendefinisikannya (mungkin dengan penginisialisasi) dalam file sumber dan meletakkan externdeklarasi di file header.

static variabel di tingkat global hanya terlihat di file sumbernya sendiri apakah variabel tersebut sampai di sana melalui penyertaan atau berada di file utama.


Catatan editor: Dalam C ++, constobjek tanpa kata kunci staticnor externdalam deklarasinya secara implisit static.

Justsalt
sumber
Saya penggemar kalimat terakhir, sangat membantu. Saya tidak memilih jawaban karena 42 lebih baik. edit: tata bahasa
RealDeal_EE'18
"Statis berarti akan ada satu salinan VAL yang dibuat untuk setiap file sumber yang disertakan di dalamnya." Itu sepertinya menyiratkan bahwa akan ada dua salinan VAL jika dua file sumber menyertakan file header. Saya berharap itu tidak benar, dan selalu ada satu contoh VAL, terlepas dari berapa banyak file yang menyertakan header.
Brent212
4
@ Brent212 Kompilator tidak tahu apakah deklarasi / definisi berasal dari file header atau file utama. Jadi harapanmu sia-sia. Akan ada dua salinan VAL jika seseorang konyol dan meletakkan definisi statis di file header dan itu dimasukkan dalam dua sumber.
Justsalt
1
nilai const memiliki hubungan internal di C ++
adrianN
112

The staticdan externtag pada variabel-file scoped menentukan apakah mereka dapat diakses dalam unit terjemahan lain (yaitu lain .catau .cppfile).

  • staticmemberikan hubungan internal variabel, menyembunyikannya dari unit terjemahan lain. Namun, variabel dengan tautan internal dapat ditentukan dalam beberapa unit terjemahan.

  • externmemberikan hubungan eksternal variabel, membuatnya terlihat oleh unit terjemahan lain. Biasanya ini berarti bahwa variabel hanya harus ditentukan dalam satu unit terjemahan.

Defaultnya (ketika Anda tidak menentukan staticatau extern) adalah salah satu area di mana C dan C ++ berbeda.

  • Di C, variabel cakupan file adalah extern(tautan eksternal) secara default. Jika Anda menggunakan C, VALis staticdan ANOTHER_VALis extern.

  • Dalam C ++, variabel cakupan file adalah static(tautan internal) secara default jika ada const, dan externsecara default jika tidak. Jika Anda menggunakan C ++, VALdan ANOTHER_VALare static.

Dari draf spesifikasi C :

6.2.2 Kaitan pengenal ... -5- Jika deklarasi pengenal untuk suatu fungsi tidak memiliki penentu kelas penyimpanan, penautannya ditentukan persis seolah-olah ia dideklarasikan dengan penentu kelas penyimpanan di luar. Jika deklarasi pengenal untuk suatu objek memiliki cakupan file dan tidak ada penentu kelas penyimpanan, tautannya bersifat eksternal.

Dari draf spesifikasi C ++ :

7.1.1 - Penentu kelas penyimpanan [dcl.stc] ... -6- Nama yang dideklarasikan dalam ruang lingkup namespace tanpa penentu kelas penyimpanan memiliki hubungan eksternal kecuali ia memiliki hubungan internal karena deklarasi sebelumnya dan asalkan tidak menyatakan const. Objek yang dideklarasikan const dan tidak secara eksplisit dideklarasikan secara eksternal memiliki keterkaitan internal.

bk1e
sumber
47

Statis akan berarti Anda mendapatkan satu salinan per file, tetapi tidak seperti yang lain mengatakan itu legal untuk melakukannya. Anda dapat dengan mudah mengujinya dengan contoh kode kecil:

test.h:

static int TEST = 0;
void test();

test1.cpp:

#include <iostream>
#include "test.h"

int main(void) {
    std::cout << &TEST << std::endl;
    test();
}

test2.cpp:

#include <iostream>
#include "test.h"

void test() {
    std::cout << &TEST << std::endl;
}

Menjalankan ini memberi Anda keluaran ini:

0x446020
0x446040

irisan kapur
sumber
5
Terima kasih atas contohnya!
Kyrol
Saya bertanya-tanya apakah TESTada const, apakah LTO akan dapat mengoptimalkannya ke dalam satu lokasi memori. Tapi -O3 -fltoGCC 8.1 tidak.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Melakukannya adalah ilegal - meskipun konstan, statis menjamin bahwa setiap instance bersifat lokal ke unit kompilasi. Itu mungkin bisa menyebariskan nilai konstanta itu sendiri jika digunakan sebagai konstanta, tetapi karena kita mengambil alamatnya, ia harus mengembalikan penunjuk unik.
irisan kapur
6

constvariabel di C ++ memiliki keterkaitan internal. Jadi, penggunaan statictidak berpengaruh.

ah

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

Jika ini adalah program C, Anda akan mendapatkan kesalahan 'definisi ganda' untuk i(karena tautan eksternal).

Nitin
sumber
2
Nah, menggunakan staticmemiliki efek yang dengan rapi menandakan niat dan kesadaran tentang apa yang dikodekan, yang tidak pernah merupakan hal yang buruk. Bagi saya ini seperti virtualsaat menimpa: kita tidak perlu melakukannya, tetapi segala sesuatunya terlihat jauh lebih intuitif - dan konsisten dengan pernyataan lain - saat kita melakukannya.
underscore_d
Anda mungkin mendapatkan kesalahan definisi ganda di C. Ini adalah perilaku tidak terdefinisi tanpa diperlukan diagnostik
MM
5

Deklarasi statis pada level kode ini berarti bahwa variabel tersebut hanya terlihat di unit kompilasi saat ini. Ini berarti bahwa hanya kode dalam modul itu yang akan melihat variabel itu.

jika Anda memiliki file header yang mendeklarasikan variabel statis dan header tersebut disertakan dalam beberapa file C / CPP, variabel tersebut akan menjadi "lokal" untuk modul tersebut. Akan ada N salinan variabel itu untuk N tempat header itu disertakan. Mereka sama sekali tidak terkait satu sama lain. Kode apa pun dalam salah satu file sumber tersebut hanya akan mereferensikan variabel yang dideklarasikan dalam modul itu.

Dalam kasus khusus ini, kata kunci 'statis' tampaknya tidak memberikan manfaat apa pun. Saya mungkin melewatkan sesuatu, tetapi tampaknya tidak masalah - Saya belum pernah melihat yang dilakukan seperti ini sebelumnya.

Sedangkan untuk sebaris, dalam hal ini variabel kemungkinan besar sebaris, tapi itu hanya karena itu dinyatakan const. Kompiler mungkin lebih cenderung memasukkan variabel statis modul, tetapi itu tergantung pada situasi dan kode yang sedang dikompilasi. Tidak ada jaminan bahwa kompilator akan menyebariskan 'statika'.

Menandai
sumber
Manfaat 'statis' di sini adalah bahwa jika tidak, Anda mendeklarasikan beberapa global semua dengan nama yang sama, satu untuk setiap modul yang menyertakan header. Jika linker tidak mengeluh, itu hanya karena dia menggigit lidahnya dan bersikap sopan.
Dalam hal ini, karena const, maka statictersirat dan karenanya opsional. Konsekuensinya adalah bahwa tidak ada kerentanan terhadap kesalahan definisi ganda seperti yang diklaim Mike F.
underscore_d
2

Untuk menjawab pertanyaan, "apakah statik berarti hanya satu salinan VAL yang dibuat, jika header disertakan oleh lebih dari satu file sumber?" ...

TIDAK . VAL akan selalu didefinisikan secara terpisah di setiap file yang menyertakan header.

Standar untuk C dan C ++ memang menyebabkan perbedaan dalam kasus ini.

Di C, variabel cakupan file adalah eksternal secara default. Jika Anda menggunakan C, VAL bersifat statis dan ANOTHER_VAL bersifat eksternal.

Perhatikan bahwa penaut modern mungkin mengeluh tentang ANOTHER_VAL jika header disertakan dalam file berbeda (nama global yang sama ditentukan dua kali), dan pasti akan mengeluh jika ANOTHER_VAL diinisialisasi ke nilai yang berbeda di file lain

Dalam C ++, variabel cakupan file bersifat statis secara default jika konstanta, dan secara default eksternal jika tidak. Jika Anda menggunakan C ++, VAL dan ANOTHER_VAL bersifat statis.

Anda juga perlu memperhitungkan fakta bahwa kedua variabel ditetapkan sebagai konst. Idealnya kompilator akan selalu memilih untuk menyebariskan variabel-variabel ini dan tidak menyertakan penyimpanan apa pun untuknya. Ada banyak sekali alasan mengapa penyimpanan dapat dialokasikan. Yang terpikir olehku ...

  • opsi debug
  • alamat yang diambil dalam file
  • kompiler selalu mengalokasikan penyimpanan (tipe const kompleks tidak dapat dengan mudah disisipkan, jadi menjadi kasus khusus untuk tipe dasar)
itj
sumber
Catatan: Di mesin abstrak ada satu salinan VAL di setiap unit terjemahan terpisah yang menyertakan header. Dalam praktiknya, linker mungkin memutuskan untuk menggabungkannya, dan kompilator mungkin mengoptimalkan beberapa atau semuanya terlebih dahulu.
MM
1

Dengan asumsi bahwa deklarasi ini berada pada cakupan global (yaitu bukan variabel anggota), maka:

statis berarti 'hubungan internal'. Dalam kasus ini, karena dideklarasikan const, ini dapat dioptimalkan / dimasukkan oleh compiler. Jika Anda menghilangkan const maka compiler harus mengalokasikan penyimpanan di setiap unit kompilasi.

Dengan menghilangkan statis , linkage secara default adalah eksternal . Sekali lagi, Anda telah disimpan oleh konst ness - kompilator dapat mengoptimalkan / penggunaan sebaris. Jika Anda menjatuhkan konstanta maka Anda akan mendapatkan kesalahan simbol multiply didefinisikan pada waktu tautan.

Seb Rose
sumber
Saya percaya kompilator harus mengalokasikan ruang untuk sebuah const int dalam semua kasus, karena modul lain selalu dapat mengatakan "extern const int apapun; sesuatu (& apapun);"
1

Anda tidak dapat mendeklarasikan variabel statis tanpa mendefinisikannya juga (ini karena pengubah kelas penyimpanan statis dan eksternal saling eksklusif). Variabel statis dapat didefinisikan dalam file header, tetapi ini akan menyebabkan setiap file sumber yang menyertakan file header memiliki salinan variabelnya sendiri, yang mungkin bukan yang dimaksudkan.

Gajendra Kumar
sumber
"... tetapi ini akan menyebabkan setiap file sumber yang menyertakan file header memiliki salinan pribadi variabel tersebut, yang mungkin bukan yang dimaksudkan." - Karena kegagalan urutan inisialisasi statis , mungkin diperlukan salinan di setiap unit terjemahan.
jww
1

Variabel const secara default statis di C ++, tetapi ekstern C. Jadi, jika Anda menggunakan C ++, konstruksi apa yang harus digunakan.

(7.11.6 C ++ 2003, dan Apexndix C memiliki sampel)

Contoh dalam membandingkan sumber kompilasi / tautan sebagai program C dan C ++:

bruziuz:~/test$ cat a.c
const int b = 22;
int main(){return 0;}
bruziuz:~/test$ cat b.c
const int b=2;
bruziuz:~/test$ gcc -x c -std=c89 a.c b.c
/tmp/ccSKKIRZ.o:(.rodata+0x0): multiple definition of `b'
/tmp/ccDSd0V3.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status
bruziuz:~/test$ gcc -x c++ -std=c++03 a.c b.c 
bruziuz:~/test$ 
bruziuz:~/test$ gcc --version | head -n1
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609
bruziuz
sumber
Ada yang rasa di masih termasuk static. Ini menandakan niat / kesadaran tentang apa yang dilakukan programmer dan mempertahankan paritas dengan jenis deklarasi lain (dan, fwiw, C) yang tidak memiliki implisit static. Ini seperti menyertakan virtualdan akhir-akhir ini overridedalam deklarasi fungsi utama - tidak perlu, tetapi lebih banyak mendokumentasikan diri sendiri dan, dalam kasus yang terakhir, kondusif untuk analisis statis.
underscore_d
Saya sangat setuju. misal saya dalam kehidupan nyata saya selalu menulisnya secara eksplisit.
bruziuz
"Jadi jika Anda menggunakan C ++ ini tidak ada pengertian konstruksi apa yang akan digunakan ..." - Hmm ... Saya baru saja menyusun proyek yang consthanya digunakan pada variabel di header dengan g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). Ini menghasilkan sekitar 150 simbol multiply didefinisikan (satu untuk setiap unit terjemahan tajuk disertakan). Saya pikir kita membutuhkan keduanya static, inlineatau namespace anonim / tidak bernama untuk menghindari hubungan eksternal.
jww
Saya mencoba baby-example dengan gcc-5.4 dengan mendeklarasikan const intdi dalam ruang lingkup namespace dan di namespace global. Dan itu dikompilasi dan mengikuti aturan "Objek menyatakan const dan tidak secara eksplisit dinyatakan eksternal memiliki hubungan internal." ".... Mungkin dalam proyek dalam beberapa alasan header ini dimasukkan ke dalam sumber yang dikompilasi C, di mana aturan yang sama sekali berbeda.
bruziuz
@jww Saya mengunggah contoh dengan masalah tautan untuk C dan tidak ada masalah untuk C ++
bruziuz
0

Statis mencegah unit kompilasi lain untuk mengeluarkan variabel tersebut sehingga kompilator bisa "menyebariskan" nilai variabel di mana ia digunakan dan tidak membuat penyimpanan memori untuknya.

Dalam contoh kedua, kompilator tidak dapat berasumsi bahwa beberapa file sumber lain tidak akan mengeluarkannya, jadi ia harus benar-benar menyimpan nilai itu dalam memori di suatu tempat.

Jim Buck
sumber
-2

Statis mencegah kompilator menambahkan banyak contoh. Ini menjadi kurang penting dengan perlindungan #ifndef, tetapi dengan asumsi header disertakan dalam dua pustaka terpisah, dan aplikasi ditautkan, dua contoh akan disertakan.

Superpolock
sumber
dengan asumsi bahwa yang Anda maksud dengan "perpustakaan" adalah unit terjemahan , maka tidak, include-guards tidak melakukan apa pun untuk mencegah definisi ganda, mengingat mereka hanya melindungi dari inklusi berulang dalam unit terjemahan yang sama . jadi, mereka tidak melakukan apa pun untuk membuat static"kurang penting". dan bahkan dengan keduanya, Anda bisa mendapatkan beberapa definisi yang terhubung secara internal, yang mungkin tidak dimaksudkan.
underscore_d