C ve C ++ 'da' sabit statik 'ne anlama geliyor?


117
const static int foo = 42;

Bunu StackOverflow'da bir kodda gördüm ve ne işe yaradığını anlayamadım. Sonra diğer forumlarda bazı karışık cevaplar gördüm. En iyi tahminim, sabiti foodiğer modüllerden gizlemek için C'de kullanılmasıdır . Bu doğru mu? Öyleyse, neden biri onu yapabileceğiniz bir C ++ bağlamında kullansın private?

Yanıtlar:


113

Hem C hem de C ++ 'da kullanımları vardır.

Tahmin ettiğiniz gibi, staticparça kapsamını bu derleme birimiyle sınırlıyor . Ayrıca statik başlatma sağlar. constsadece derleyiciye kimsenin onu değiştirmesine izin vermemesini söyler. Bu değişken, mimariye bağlı olarak veriye veya bss segmentine yerleştirilir ve salt okunur olarak işaretlenmiş bellekte olabilir.

Tüm bunlar, C'nin bu değişkenleri nasıl ele aldığıdır (veya C ++ ad alanı değişkenlerini nasıl ele aldığıdır). C ++ 'da, işaretlenen bir üye static, belirli bir sınıfın tüm örnekleri tarafından paylaşılır. Özel olup olmaması, bir değişkenin birden çok örnek tarafından paylaşıldığı gerçeğini etkilemez. constOrada olması , herhangi bir kodun onu değiştirmeye çalışacağı konusunda sizi uyaracaktır.

Kesinlikle özel olsaydı, sınıfın her bir örneği kendi sürümünü alırdı (eniyileyiciye bakılmaksızın).


1
Orijinal örnek bir "özel değişken" den bahsediyor. Bu nedenle, bu bir mebmerdir ve statik, bağlantı üzerinde hiçbir etkiye sahip değildir . "Statik kısım, kapsamını bu dosyayla sınırlandırır" ı kaldırmalısınız.
Richard Corden

"Özel bölüm", açık "dizgiler" ve genel diziler gibi diğer tüm küresel değişkenlerle paylaştığı veri bölümü olarak bilinir. Bu, kod segmentine zıttır.
spoulson

@Richard - bir sınıfın üyesi olduğunu düşündüren nedir? Soruda öyle olduğunu söyleyen hiçbir şey yok. Eğer bir sınıfın üyesiyse, haklısınız, ancak küresel kapsamda beyan edilen bir değişkense, Chris haklıdır.
Graeme Perrow

1
Orijinal poster özelden olası daha iyi bir çözüm olarak bahsetti, ancak asıl sorun olarak değil.
Chris Arguin

@Graeme, tamam bu yüzden "kesinlikle" bir üye değil - ancak, bu cevap sadece isim alanı üyeleri için geçerli olan ifadeler yapıyor ve bu ifadeler üye değişkenleri için yanlış. Oyların miktarı göz önüne alındığında, bu hata, dile pek aşina olmayan birinin kafasını karıştırabilir - düzeltilmesi gerekir.
Richard Corden

212

Birçok insan temel bir cevap verdi ama kimse C ++ içinde olduğuna işaret constvarsayılan staticolarak namespacedüzeyinde (ve bazı yanlış bilgi verdi). C ++ 98 standart bölümü 3.5.3'e bakın.

Önce biraz arka plan:

Çeviri birimi: Ön işlemciden sonra (özyinelemeli olarak) tüm içerme dosyalarını içeren bir kaynak dosya.

Statik bağlantı: Bir sembol yalnızca çeviri biriminde bulunur.

Harici bağlantı: Diğer çeviri birimlerinden bir sembol mevcuttur.

at namespacedüzeyinde

Bu, global değişkenler olarak adlandırılan global ad alanını içerir .

static const int sci = 0; // sci is explicitly static
const int ci = 1;         // ci is implicitly static
extern const int eci = 2; // eci is explicitly extern
extern int ei = 3;        // ei is explicitly extern
int i = 4;                // i is implicitly extern
static int si = 5;        // si is explicitly static

İşlev düzeyinde

staticdeğerin işlev çağrıları arasında korunduğu anlamına gelir.
Fonksiyon staticdeğişkenlerinin anlambilimleri , programın veri segmentinde (yığın veya yığın değil) yer almaları bakımından global değişkenlere benzer, değişkenlerin yaşam süreleri hakkında daha fazla ayrıntı için bu soruya bakın static.

at classdüzeyinde

staticdeğerin sınıfın tüm örnekleri arasında paylaşıldığı ve constdeğişmediği anlamına gelir.


2
Fonksiyon seviyesinde: Statik const ile de çok fazla değildir, bunlar farklı davranabilir const int *foo(int x) {const int b=x;return &b};karşıconst int *foo(int x) {static const int b=x;return &b};
Hanczar

1
Soru hem C hem de C ++ ile ilgili olduğundan, constyalnızca staticikincisini ima etmekle ilgili bir not eklemelisiniz .
Nikolai Ruhe

@Motti: Harika yanıt. Bir işlev düzeyinde neyi gereksiz kılan şeyin ne olduğunu açıklayabilir misiniz? constBeyanın staticorayı da ima ettiğini mi söylüyorsunuz ? Olduğu gibi, atarsanız constve değeri değiştirirseniz, tüm değerler değiştirilecek mi?
Cookie

1
@Motti const, işlev düzeyinde statik anlamına gelmez, bu eşzamanlılık kabusu olur (const! = Sabit ifade), işlev düzeyindeki her şey örtüktür auto. Bu soru da [c] olarak etiketlendiğinden, küresel bir düzeyin const intdolaylı externolarak C olduğunu belirtmeliyim. Bununla birlikte, burada sahip olduğunuz kurallar C ++ ' yı mükemmel bir şekilde tanımlamaktadır.
Ryan Haining

1
Ve C ++ 'da, her üç durumda staticda değişkenin statik süre olduğunu (programın başından sonuna kadar süren yalnızca bir kopya vardır) ve aksi belirtilmedikçe dahili / statik bağlantıya sahip olduğunu gösterir (bu, işlevin yerel statik değişkenler için bağlantı veya statik üyeler için sınıf bağlantısı). Temel farklılıklar static, geçerli olduğu her durumda bunun ne anlama geldiğidir.
Justin Time - Monica'yı Yeniden

45

Bu kod satırı aslında birkaç farklı bağlamda görünebilir ve yaklaşık olarak aynı şekilde davranmasına rağmen küçük farklılıklar vardır.

Ad Alanı Kapsamı

// foo.h
static const int i = 0;

iBaşlığı içeren her çeviri biriminde ' ' görünecektir. Ancak, nesnenin adresini gerçekten kullanmadığınız sürece (örneğin. ' &i'), Derleyicinin ' i' basit bir tür güvenli olarak ele alınacağından oldukça eminim 0. İki çeviri birimi daha ' &i' kelimesini aldığında, adres her çeviri birimi için farklı olacaktır.

// foo.cc
static const int i = 0;

' i' dahili bağlantıya sahiptir ve bu nedenle bu çeviri biriminin dışından atıfta bulunulamaz. Bununla birlikte, adresini kullanmadığınız sürece, büyük olasılıkla tür güvenli olarak değerlendirilecektir 0.

Dikkat çekmeye değer bir şey şu ki, aşağıdaki beyan:

const int i1 = 0;

ile tamamen aynıdır static const int i = 0. İle bildirilen constve açıkça bildirilmeyen bir ad alanındaki bir değişken externörtük olarak statiktir. Bunu düşünürseniz, C ++ komitesinin amacı , ODR'yi bozmamak için consther zaman staticanahtar sözcüğe ihtiyaç duymadan değişkenlerin başlık dosyalarında bildirilmesine izin vermekti .

Sınıf Kapsamı

class A {
public:
  static const int i = 0;
};

Yukarıdaki örnekte standart i, adresi gerekmiyorsa " " nin tanımlanmasına gerek olmadığını açıkça belirtir . Diğer bir deyişle, itür açısından güvenli 0 olarak yalnızca ' ' kullanırsanız , derleyici bunu tanımlamaz. Sınıf ve ad alanı sürümleri arasındaki bir fark, ' i' adresinin (iki veya daha fazla çeviri biriminde kullanılıyorsa) sınıf üyesi için aynı olmasıdır. Adresin kullanıldığı yerde, bunun için bir tanımınız olmalıdır:

// a.h
class A {
public:
  static const int i = 0;
};

// a.cc
#include "a.h"
const int A::i;            // Definition so that we can take the address

2
Statik sabitin yalnızca ad alanı kapsamındaki const ile aynı olduğuna işaret etmek için +1.
Plumenator

"Foo.h" veya "foo.cc" içine yerleştirmek arasında bir fark yoktur çünkü .h, çeviri birimi derlenirken basitçe dahil edilir.
Mikhail

2
@Mikhail: Haklısın. Bir başlığın birden fazla TU'ya dahil edilebileceği varsayımı vardır ve bu nedenle bundan ayrı olarak bahsetmek faydalı olmuştur.
Richard Corden

24

Küçük bir alan optimizasyonu.

Dediğinde

const int foo = 42;

Bir sabit tanımlamıyorsunuz, ancak salt okunur bir değişken oluşturuyorsunuz. Derleyici, foo'yu her gördüğünde 42'yi kullanacak kadar akıllıdır, ancak bunun için başlatılan veri alanında alan da tahsis edecektir. Bu, tanımlandığı gibi, foo'nun harici bağlantısı olduğu için yapılır. Başka bir derleme birimi şunu söyleyebilir:

extern const int foo;

Değerine erişmek için. Bu derleme biriminin foo'nun değerinin ne olduğu hakkında hiçbir fikri olmadığı için bu iyi bir uygulama değildir. Bunun bir const int olduğunu bilir ve her kullanıldığında değeri bellekten yeniden yüklemesi gerekir.

Şimdi, statik olduğunu bildirerek:

static const int foo = 42;

Derleyici her zamanki optimizasyonunu yapabilir, ancak "hey, bu derleme biriminin dışındaki hiç kimse foo'yu göremez ve bunun her zaman 42 olduğunu biliyorum, bu yüzden onun için herhangi bir yer ayırmaya gerek yok" diyebilir.

Ayrıca, C ++ 'da, adların mevcut derleme biriminden kaçmasını önlemenin tercih edilen yolunun anonim bir ad alanı kullanmak olduğunu da not etmeliyim:

namespace {
    const int foo = 42; // same as static definition above
}

1
, statik kullanmadan bahsetmişken, "bunun için başlatılmış veri alanında da yer ayıracaktır". ve statik kullanarak "onun için herhangi bir alan ayırmaya gerek yok". (derleyici vali nereden seçiyor?) değişkenin depolandığı yığın ve yığın terimiyle açıklayabilir misiniz? Yorumluyorsam beni düzeltin yanlış.
Nihar

@ N.Nihar - Statik veri alanı, statik bağlantıya sahip tüm verileri içeren sabit boyutlu bir bellek parçacığıdır. Programın belleğe yüklenmesi işlemi tarafından "tahsis edilir". Yığının veya yığının bir parçası değil.
Ferruccio

Foo'ya bir işaretçi döndüren bir işlevim varsa ne olur? Optimizasyonu bozuyor mu?
nw.

@nw: Evet, gerekirdi.
Ferruccio

8

Bir "int" eksik. Olmalı:

const static int foo = 42;

C ve C ++ 'da, 42 değeri yerel dosya kapsamına sahip bir tamsayı sabiti bildirir.

Neden 42? Zaten bilmiyorsanız (ve bilmediğinize inanmak zorsa), bu Yaşama, Evrene ve Her Şeye Yanıt'a bir göndermedir .


Teşekkürler ... şimdi her zaman ... hayatımın geri kalanı boyunca ... 42'yi gördüğümde, bunu hep merak edeceğim. haha
Inisheer

Bu, evrenin 13 parmakla yaratıldığının kanıtıdır (soru ve cevap aslında 13. tabanda eşleşir).
paxdiablo

Fareler. Her ayağınızda 3 ayak parmağı, artı bir kuyruk size temel 13'ü verir.
KeithB

Aslında bir bildiride 'int'e ihtiyacınız yok, ancak bunu yazmak kesinlikle zevkli. C her zaman varsayılan olarak 'int' türlerini varsayar. Dene!
ephemient

"42 değerinin yerel dosya kapsamına sahip" ?? yoksa tüm derleme birimi için mi?
aniliitb10

4

C ++ 'da,

static const int foo = 42;

sabitleri tanımlamanın ve kullanmanın tercih edilen yoludur. Bunu kullanmak yerine

#define foo 42

çünkü tip güvenlik sistemini bozmaz.


4

Tüm harika cevaplara küçük bir ayrıntı eklemek istiyorum:

Eklentiler yazarsanız (örneğin, bir CAD sistemi tarafından yüklenecek DLL'ler veya .so kitaplıkları), statik , bunun gibi ad çakışmalarını önleyen bir hayat kurtarıcıdır:

  1. CAD sistemi, "const int foo = 42;" olan bir A eklentisi yükler. içinde.
  2. Sistem, "const int foo = 23;" olan bir B eklentisi yükler. içinde.
  3. Sonuç olarak, eklenti B, foo için 42 değerini kullanacaktır, çünkü eklenti yükleyici, harici bağlantı ile zaten bir "foo" olduğunu anlayacaktır.

Daha da kötüsü: 3. Adım, derleyici optimizasyonuna, eklenti yükleme mekanizmasına vb. Bağlı olarak farklı davranabilir.

Bu sorunu bir kez iki eklentide iki yardımcı işlevle (aynı ad, farklı davranış) yaşadım. Bunları statik ilan etmek sorunu çözdü.


İki eklenti arasındaki ad çakışmalarında bir şeyler garip görünüyordu, bu da beni, m_hDfltHeap'i harici bağlantıya sahip bir tutamaç olarak tanımlayan birçok DLL'imden birinin bağlantı haritasını incelemeye yönlendirdi. Yeterince elbette, bağlantı haritasında _m_hDfltHeap olarak listelenen tüm dünyanın görmesi ve kullanması için orada. Bu gerçeği tamamen unutmuştum.
David A. Gray

4

C99 / GNU99 spesifikasyonuna göre:

  • static

    • depolama sınıfı tanımlayıcısıdır

    • dosya seviyesi kapsamındaki nesneler varsayılan olarak harici bağlantıya sahiptir

    • Statik belirticiye sahip dosya düzeyinde kapsam nesneleri dahili bağlantıya sahiptir
  • const

    • tür niteleyicidir (türün bir parçasıdır)

    • hemen sol örneğe uygulanan anahtar kelime - ör.

      • MyObj const * myVar; - const nitelikli nesne türüne nitelenmemiş işaretçi

      • MyObj * const myVar; - nitelenmemiş nesne türüne yönelik const nitelikli işaretçi

    • En soldaki kullanım - değişkene değil nesne türüne uygulanır

      • const MyObj * myVar; - const nitelikli nesne türüne nitelenmemiş işaretçi

BÖYLECE:

static NSString * const myVar; - dahili bağlantı ile değişmez dizgeye sabit işaretçi.

staticAnahtar kelimenin olmaması değişken adını global yapar ve uygulama içinde ad çakışmalarına yol açabilir.


4

C ++ 17 inlinedeğişkenler

Google'da "C ++ const static" yazdıysanız, gerçekten kullanmak istediğiniz şey büyük olasılıkla C ++ 17 satır içi değişkenlerdir .

Bu harika C ++ 17 özelliği şunları yapmamızı sağlar:

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Derleyin ve çalıştırın:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub yukarı akış .

Ayrıca bkz: Satır içi değişkenler nasıl çalışır?

Satır içi değişkenlerde C ++ standardı

C ++ standardı, adreslerin aynı olacağını garanti eder. C ++ 17 N4659 standart taslak 10.1.6 "Satır içi tanımlayıcı ":

6 Dış bağlantılı bir satır içi işlev veya değişken, tüm çeviri birimlerinde aynı adrese sahip olmalıdır.

cppreference https://en.cppreference.com/w/cpp/language/inline , staticverilmezse, harici bağlantıya sahip olduğunu açıklar .

GCC satır içi değişken uygulaması

Nasıl uygulandığını şu şekilde gözlemleyebiliriz:

nm main.o notmain.o

içerenler:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

ve man nmşöyle diyor u:

"u" Sembol, benzersiz bir küresel semboldür. Bu, standart ELF simge bağlamaları kümesinin bir GNU uzantısıdır. Böyle bir sembol için dinamik bağlayıcı, tüm süreçte bu isim ve türe sahip tek bir sembolün kullanımda olduğundan emin olacaktır.

bu yüzden bunun için özel bir ELF uzantısı olduğunu görüyoruz.

Ön-C ++ 17: extern const

C ++ 17'den önce ve C'de, bir ile çok benzer bir etki elde edebiliriz extern const, bu da tek bir bellek konumunun kullanılmasına yol açar.

Olumsuz yönler inline:

  • değişkeni constexprbu teknikle yapmak mümkün değildir , sadece şunainline izin verir: constexpr extern nasıl bildirilir?
  • değişkeni başlık ve cpp dosyasında ayrı ayrı bildirmeniz ve tanımlamanız gerektiğinden daha az zariftir

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub yukarı akış .

Pre-C ++ 17 yalnızca başlık alternatifleri

Bunlar externçözüm kadar iyi değil , ancak işe yarıyor ve yalnızca tek bir bellek konumunu kaplıyor:

Bir constexprişlev, çünkü tanımın her çeviri biriminde görünmesini constexprima ederinline ve buna inline izin verir (zorlar) :

constexpr int shared_inline_constexpr() { return 42; }

ve eminim ki herhangi bir düzgün derleyici çağrıyı sıraya dizecektir.

Ayrıca aşağıdaki gibi bir constveya constexprstatik değişken de kullanabilirsiniz :

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

ancak adresini almak gibi şeyler yapamazsınız, aksi takdirde odr kullanılır hale gelir, ayrıca bakınız: constexpr statik veri üyelerini tanımlama

C

C'de durum C ++ ön C ++ 17 ile aynıdır, şuraya bir örnek yükledim: C'de "statik" ne anlama geliyor?

Tek fark, C ++ ' constda staticküreselleri ifade etmesidir, ancak C: C ++' statik sabit 've' sabit 'anlambilimlerinde yoktur.

Tamamen satır içi yapmanın bir yolu var mı?

TODO: Herhangi bir bellek kullanmadan değişkeni tam olarak satır içi yapmanın bir yolu var mı?

Önişlemcinin yaptığı gibi.

Bu bir şekilde şunları gerektirir:

  • değişkenin adresi alınırsa yasaklamak veya tespit etmek
  • bu bilgiyi ELF nesne dosyalarına ekleyin ve LTO'nun bunu optimize etmesine izin verin

İlişkili:

Ubuntu 18.10, GCC 8.2.0'da test edilmiştir.


2

Evet, bir modüldeki bir değişkeni diğer modüllerden gizler. C ++ 'da, diğer dosyaların gereksiz bir şekilde yeniden oluşturulmasını tetikleyecek bir .h dosyasını değiştirmek istemediğimde / değiştirmem gerektiğinde kullanırım. Ayrıca, önce durağı koyarım:

static const int foo = 42;

Ayrıca, kullanımına bağlı olarak, derleyici onun için depolama alanı bile ayırmaz ve yalnızca kullanıldığı yerde "satır içi" değer sağlar. Statik olmadan, derleyici başka bir yerde kullanılmadığını varsayamaz ve satır içi olamaz.


2

Bu, yalnızca derleme modülünde (.cpp dosyası) görülebilir / erişilebilir genel bir sabittir. Bu amaçla statik kullanan BTW kullanımdan kaldırılmıştır. Anonim bir ad alanı ve bir enum kullanmak daha iyi:

namespace
{
  enum
  {
     foo = 42
  };
}

bu, derleyiciyi foo'yu sabit olarak değerlendirmemeye zorlar ve bu nedenle optimizasyonu engeller.
Nils Pipenbrinck

numaralandırma değerleri her zaman sabittir, bu nedenle bunun herhangi bir optimizasyonu nasıl engelleyeceğini
anlamıyorum

ah - doğru .. benim hatam. basit bir int değişkeni kullandığınızı düşündüm.
Nils Pipenbrinck

Roskoto, enumbu bağlamda ne gibi bir faydası olduğunu bilmiyorum . Detaylandırmak ister misin? Bunlar enumsgenellikle yalnızca derleyicinin değer için herhangi bir alan ayırmasını engellemek için (modern derleyicilerin bunun için bu enumhacklemeye ihtiyacı olmasa da) ve değere işaretçilerin oluşturulmasını önlemek için kullanılır.
Konrad Rudolph

Konrad, Bu durumda bir numaralandırma kullanırken tam olarak ne sorun görüyorsunuz? Sabit girişlere ihtiyaç duyduğunuzda numaralandırmalar kullanılır, bu tam olarak böyledir.
Roskoto

1

Özel hale getirmek, yine de başlıkta göründüğü anlamına gelir. Çalışan "en zayıf" yolu kullanma eğilimindeyim. Scott Meyers tarafından yazılan bu klasik makaleye bakın: http://www.ddj.com/cpp/184401197 (bu işlevlerle ilgilidir, ancak burada da uygulanabilir).

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.