Başlık dosyalarındaki değişken bildirimleri - statik mi değil mi?


92

Bazılarını yeniden düzenlerken #defines, C ++ başlık dosyasında aşağıdakine benzer bildirimlerle karşılaştım:

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

Soru şu ki, eğer varsa, statik ne fark eder? Klasik #ifndef HEADER #define HEADER #endifnumara nedeniyle (önemliyse) başlıkların birden fazla eklenmesinin mümkün olmadığını unutmayın.

Statik VAL, üstbilginin birden fazla kaynak dosya tarafından dahil edilmesi durumunda yalnızca bir kopyasının oluşturulduğu anlamına mı gelir ?


Yanıtlar:


107

Bu , dahil edildiği her kaynak dosya için yaratılanın staticbir kopyasının olacağı anlamına gelir VAL. Ancak bu aynı zamanda, birden fazla dahil etmenin VALbağlantı anında birden fazla tanımın çakışmasına neden olmayacağı anlamına gelir . C'de, olmadan , diğer kaynak dosyalar onu staticbildirirken yalnızca bir kaynak dosyanın tanımlandığından emin olmanız gerekir . Genellikle bunu bir kaynak dosyada tanımlayarak (muhtemelen bir başlatıcıyla) yapar ve bildirimi bir başlık dosyasına koyar .VALexternextern

static genel düzeydeki değişkenler, ister içerme yoluyla ister ana dosyada olsun, yalnızca kendi kaynak dosyalarında görülebilir.


Editörün notu: C ++ 'da, bildirimlerinde ne anahtar kelimeleri constne staticde externanahtar kelimeleri olan nesneler örtük olarak bulunur static.


Son cümlenin hayranıyım, inanılmaz derecede yardımcı oldum. Cevabı oylamadım çünkü 42 daha iyi. düzenleme: gramer
RealDeal_EE'18

"Statik, dahil olduğu her kaynak dosya için bir VAL kopyası oluşturulacağı anlamına gelir." Bu, başlık dosyasını iki kaynak dosya içeriyorsa, VAL'nin iki kopyası olacağı anlamına geliyor gibi görünüyor. Bunun doğru olmadığını ve başlıkta kaç dosyanın bulunduğuna bakılmaksızın her zaman tek bir VAL örneği olduğunu umuyorum.
Brent212

4
@ Brent212 Derleyici, bir bildirimin / tanımın bir başlık dosyasından mı yoksa ana dosyadan mı geldiğini bilmiyor. Yani boşuna umut ediyorsun. Birisi aptalca davrandıysa ve bir başlık dosyasına statik bir tanım koyarsa ve iki kaynağa dahil edilirse, VAL'nin iki kopyası olacaktır.
Justsalt

1
const değerleri C ++ 'da dahili bağlantıya sahip
adrianN

112

staticVe externdosya kapsamlı değişkenler üzerindeki etiketleri onlar (yani diğer diğer çeviri birimlerinde erişilebilir olup olmadığını belirlemek .cveya .cppdosyaları).

  • staticdeğişken dahili bağlantıyı diğer çeviri birimlerinden gizleyerek verir. Bununla birlikte, dahili bağlantılı değişkenler birden çok çeviri biriminde tanımlanabilir.

  • externdeğişken dış bağlantı verir ve onu diğer çeviri birimlerine görünür kılar. Tipik olarak bu, değişkenin yalnızca bir çeviri biriminde tanımlanması gerektiği anlamına gelir.

Varsayılan ( staticveya belirtmediğinizde extern), C ve C ++ 'nın farklı olduğu alanlardan biridir.

  • C'de, dosya kapsamlı değişkenler externvarsayılan olarak (harici bağlantı) 'dır. C kullanıyorsanız, VALöyle staticve ANOTHER_VALöyle extern.

  • C ++ dosya kapsamlı değişkenler staticolduklarını varsayılan olarak (iç bağlantı) constve externvarsayılan olarak bunlar değilse. Eğer kullanıyorsanız C ++, hem VALve ANOTHER_VALvardır static.

C spesifikasyonunun bir taslağından :

6.2.2 Tanımlayıcıların bağlantıları ... -5- Bir fonksiyon için bir tanımlayıcının bildiriminin depolama sınıfı tanımlayıcısı yoksa, bağlantısı tam olarak depolama sınıfı extern ile bildirilmiş gibi belirlenir. Bir nesnenin tanımlayıcısının bildirimi dosya kapsamına sahipse ve depolama sınıfı belirticisi yoksa, bağlantısı haricidir.

C ++ spesifikasyonunun bir taslağından :

7.1.1 - Depolama sınıfı tanımlayıcıları [dcl.stc] ... -6- Depolama sınıfı tanımlayıcısı olmadan bir ad alanı kapsamında bildirilen bir ad, önceki bir bildirimden dolayı dahili bir bağlantıya sahip olmadığı ve olmaması koşuluyla harici bağlantıya sahiptir. ilan edilen sabit Const olarak bildirilen ve açıkça belirtilmeyen extern nesnelerinin iç bağlantıları vardır.


47

Statik, dosya başına bir kopya alacağınız anlamına gelir, ancak diğerlerinin aksine, bunu yapmanın tamamen yasal olduğunu söyledi. Bunu küçük bir kod örneğiyle kolayca test edebilirsiniz:

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

Bunu çalıştırmak size şu çıktıyı verir:

0x446020
0x446040


5
Örnek için teşekkürler!
Kyrol

Acaba TESTvardı constLTO tek bellek konuma optimize etmek mümkün olacaktır eğer. Ancak -O3 -fltoGCC 8.1'in olmadı.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Bunu yapması yasa dışı olur - sabit olsa bile, statik, her örneğin derleme birimi için yerel olduğunu garanti eder. Sabit olarak kullanıldığında muhtemelen sabit değerin kendisini satır içi yapabilir, ancak adresini aldığımız için benzersiz bir gösterici döndürmesi gerekir.
dilimlenmiş kireç

6

constC ++ 'daki değişkenler dahili bağlantıya sahiptir. Yani kullanmanın bir staticetkisi yoktur.

Ah

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

Bu bir C programı olsaydı, i(harici bağlantı nedeniyle) için 'çoklu tanım' hatası alırdınız.


2
Eh, kullanmak static, neyi kodladığına dair düzgün bir şekilde niyeti ve farkındalığı işaret etme etkisine sahiptir ki bu asla kötü bir şey değildir. Bana göre bu, virtualgeçersiz kılma zamanını dahil etmek gibidir : mecbur değiliz, ama yaptığımızda şeyler çok daha sezgisel ve diğer beyanlarla tutarlı görünüyor.
underscore_d

Sen belki Hiçbir teşhis gerekli olan tanımsız davranıştır C. birden tanım hatası alıyorum
MM

5

Bu kod seviyesindeki statik bildirim, değişken etiketin yalnızca mevcut derleme biriminde görülebileceği anlamına gelir. Bu, yalnızca o modül içindeki kodun bu değişkeni göreceği anlamına gelir.

Değişken bir statik bildiren bir başlık dosyanız varsa ve bu başlık birden çok C / CPP dosyasına dahil edilmişse, bu değişken bu modüller için "yerel" olacaktır. Başlığın dahil olduğu N yer için bu değişkenin N kopyası olacaktır. Birbirleriyle hiçbir ilgileri yoktur. Bu kaynak dosyalardaki herhangi bir kod, yalnızca o modül içinde bildirilen değişkene başvurur.

Bu özel durumda, 'statik' anahtar kelime herhangi bir fayda sağlamıyor gibi görünüyor. Bir şeyi kaçırıyor olabilirim, ama önemli değil gibi görünüyor - daha önce böyle yapılan bir şey görmemiştim.

Satır içi yapmaya gelince, bu durumda değişken büyük olasılıkla satır içi çizgidir, ancak bunun nedeni yalnızca sabit olarak bildirilmesidir. Derleyici , modül statik değişkenlerini satır içi yapma olasılığı daha yüksek olabilir , ancak bu duruma ve derlenen koda bağlıdır. Derleyicinin 'statikleri' satır içi yapacağına dair bir garanti yoktur.


Buradaki "statik" özelliğinin avantajı, aksi takdirde, başlık içeren her modül için bir tane olmak üzere tümü aynı ada sahip birden çok global bildirmenizdir. Bağlayıcı şikayet etmezse, bunun nedeni dilini ısırması ve kibar olmasıdır.

Nedeniyle bu durumda, const, staticima ve dolayısıyla isteğe edilir. Sonuç olarak, Mike F'nin iddia ettiği gibi çoklu tanımlama hatalarına duyarlılık yoktur.
underscore_d


2

Soruyu yanıtlamak için, "Statik, üstbilginin birden fazla kaynak dosya tarafından dahil edilmesi durumunda VAL'nin yalnızca bir kopyasının oluşturulduğu anlamına mı gelir?" ...

HAYIR . VAL, başlığı içeren her dosyada her zaman ayrı olarak tanımlanacaktır.

C ve C ++ standartları bu durumda bir farklılığa neden olur.

C'de, dosya kapsamlı değişkenler varsayılan olarak externdir. C kullanıyorsanız, VAL statiktir ve ANOTHER_VAL haricidir.

Modern bağlayıcıların, başlık farklı dosyalara dahil edilirse (aynı genel ad iki kez tanımlanır) ANOTHER_VAL hakkında şikayet edebileceğini ve ANOTHER_VAL başka bir dosyada farklı bir değere başlatıldığında kesinlikle şikayet edebileceğini unutmayın.

C ++ 'da, dosya kapsamlı değişkenler, sabit ise varsayılan olarak statiktir, değilse varsayılan olarak extern'dir. C ++ kullanıyorsanız, hem VAL hem de ANOTHER_VAL statiktir.

Ayrıca, her iki değişkenin de sabit olarak tanımlandığını dikkate almanız gerekir. İdeal olarak, derleyici her zaman bu değişkenleri satır içi yapmayı seçer ve onlar için herhangi bir depolama alanı içermez. Depolamanın tahsis edilebilmesi için birçok neden vardır. Aklıma gelenler ...

  • hata ayıklama seçenekleri
  • dosyada alınan adres
  • derleyici her zaman depolama alanı ayırır (karmaşık sabit türleri kolayca satır içine alınamaz, bu nedenle temel türler için özel bir durum haline gelir)

Not: Soyut makinede , başlığı içeren her bir ayrı çeviri biriminde bir VAL kopyası vardır. Pratikte, bağlayıcı bunları yine de birleştirmeye karar verebilir ve derleyici önce bunların bir kısmını veya tamamını optimize edebilir.
MM

1

Bu bildirimlerin genel kapsamda olduğunu (yani üye değişkenler olmadığını) varsayarsak, o zaman:

statik , 'iç bağlantı' anlamına gelir. Bu durumda, const olarak bildirildiğinden, bu derleyici tarafından optimize edilebilir / satır içine alınabilir. Sabit değerini atlarsanız, derleyicinin her bir derleme biriminde depolama alanı ayırması gerekir.

Statik atlandığında bağlantı varsayılan olarak harici olur . Yine, tarafından kaydedilmiş oldum const derleyici optimize edebilirsiniz / satır içi kullanımı - lık. Eğer düşürürseniz const o zaman bir alacak Çarp tanımlanan semboller bağlantı zaman hata.


Derleyicinin her durumda const int için alan ayırması gerektiğine inanıyorum, çünkü başka bir modül her zaman "extern const int her neyse; bir şey (& her neyse);"

1

Statik bir değişkeni tanımlamadan da bildiremezsiniz (bunun nedeni, statik ve extern depolama sınıfı değiştiricilerinin birbirini dışlamasıdır). Statik bir değişken, bir başlık dosyasında tanımlanabilir, ancak bu, başlık dosyasını içeren her kaynak dosyasının değişkenin kendi özel kopyasına sahip olmasına neden olur, ki bu muhtemelen amaçlanan şey değildir.


"... ancak bu, başlık dosyasını içeren her kaynak dosyasının değişkenin kendi özel kopyasına sahip olmasına neden olur, ki bu muhtemelen amaçlanan şey değildir." - Statik başlatma sipariş fiyaskosu nedeniyle , her çeviri biriminde bir kopyasının olması gerekebilir.
jww

1

const değişkenleri varsayılan olarak C ++ 'da statiktir, ancak extern C. Dolayısıyla, C ++ kullanırsanız, hangi yapının kullanılacağı anlamsızdır.

(7.11.6 C ++ 2003 ve Apexndix C'de örnekler var)

C ve C ++ programı olarak derleme / bağlantı kaynaklarını karşılaştırma örneği:

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

Orada olduğu hala dahil olmak üzere duygusu static. Programcının ne yaptığına dair niyet / farkındalığa işaret eder ve örtük olmayan diğer bildirim türleri (ve fwiw, C) ile eşitliği korur static. Bu , geçersiz kılan işlevlerin bildirimlerini dahil etmek virtualve son zamanlarda bunlara dahil etmek gibidir override- gerekli değildir, ancak çok daha fazla kendi kendini belgelendirir ve ikincisi durumunda statik analize elverişlidir.
underscore_d

Kesinlikle katılıyorum. Örneğin, gerçek hayatta bana gelince, her zaman açıkça yazıyorum.
bruziuz

"Yani C ++ kullanırsanız, bu hangi yapının kullanılacağı konusunda bir anlam ifade etmiyor ..." - Hmm ... Sadece constbaşlıktaki bir değişken üzerinde kullanılan bir proje derledim g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). Yaklaşık 150 çoklu tanımlı sembolle sonuçlandı (başlığın dahil edildiği her çeviri birimi için bir tane). Bence ya gerekmez düşünüyorum static, inlineya anonim / isimsiz ad harici bağlantı önlemek için.
jww

Gcc-5.4 ile baby-example'ı const intad alanı kapsamı içinde ve genel ad alanında beyan ile denedim . Ve derlendi ve "Sabit olarak bildirilen ve açıkça belirtilmeyen harici nesnelerin dahili bağlantıya sahip olduğu" kuralını izleyin. ".... Belki projede bir nedenden ötürü bu başlık, kuralların tamamen farklı olduğu C derlenmiş kaynaklara dahil edilmiştir.
bruziuz

@jww C için bağlantı sorunu olan ve C ++ için sorun olmayan bir örnek yükledim
bruziuz

0

Statik, başka bir derleme biriminin bu değişkeni dışlamasını engeller, böylece derleyici değişkenin kullanıldığı yerde yalnızca "satır içi" olabilir ve onun için bellek deposu oluşturmaz.

İkinci örneğinizde, derleyici başka bir kaynak dosyanın onu dışarıda bırakmayacağını varsayamaz, bu nedenle aslında bu değeri bellekte bir yerde saklamalıdır.


-2

Statik, derleyicinin birden çok örnek eklemesini engeller. Bu, #ifndef korumasıyla daha az önemli hale gelir, ancak başlığın iki ayrı kitaplığa dahil edildiği ve uygulamanın bağlantılı olduğu varsayıldığında, iki örnek dahil edilecektir.


"Kütüphaneler" ile çeviri birimlerini kastettiğinizi varsayarsak , o halde hayır, dahil etme korumaları, yalnızca aynı çeviri birimi içinde tekrarlanan eklemelere karşı koruma sağladıklarını görerek, birden fazla tanımı önlemek için kesinlikle hiçbir şey yapmazlar . bu yüzden static"daha az önemli" hale getirmek için hiçbir şey yapmazlar . ve hatta her ikisiyle bile, muhtemelen amaçlanmayan birden fazla dahili olarak bağlantılı tanım elde edebilirsiniz.
underscore_d
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.