CおよびC ++で「const static」とはどういう意味ですか?


117
const static int foo = 42;

StackOverflowの一部のコードでこれを確認しましたが、何ができるのかわかりませんでした。それから私は他のフォーラムでいくつかの混乱した答えを見ました。私の推測では、foo他のモジュールから定数を隠すためにCで使用されていると思います。これは正しいです?もしそうなら、なぜ誰もがあなたがそれを作ることができるC ++コンテキストでそれを使うのprivateですか?

回答:


113

CとC ++の両方で使用されます。

ご想像のとおり、staticパーツのスコープはそのコンパイルユニットに限定されます。また、静的な初期化も提供します。const誰かに変更させないようコンパイラーに指示するだけです。この変数は、アーキテクチャに応じてデータまたはbssセグメントに配置され、読み取り専用とマークされたメモリ内にある可能性があります。

以上が、Cがこれらの変数を処理する方法(またはC ++が名前空間変数を処理する方法)です。C ++では、マークstaticされたメンバーは、特定のクラスのすべてのインスタンスで共有されます。プライベートかどうかは、1つの変数が複数のインスタンスで共有されるという事実に影響を与えません。持つconst任意のコードがそれを修正しようとする場合があるにして警告を表示します。

完全にプライベートの場合、クラスの各インスタンスは独自のバージョンを取得します(オプティマイザにもかかわらず)。


1
元の例は「プライベート変数」について話しています。したがって、これはmebmerであり、staticはリンケージに影響を与えません。「静的部分はそのファイルのスコープを制限します」を削除する必要があります。
Richard Corden

「特別なセクション」はデータセグメントと呼ばれ、明示的な「文字列」やグローバル配列など、他のすべてのグローバル変数と共有されます。これはコードセグメントとは反対です。
スポールソン2008年

@リチャード-クラスのメンバーだと思う理由は何ですか?それがそうであると言う質問には何もありません。場合、それはクラスのメンバーで、その後、あなたは正しいが、それはただだ場合、変数はグローバルスコープで宣言、そしてクリスは正しいです。
Graeme Perrow

1
元の投稿者は、可能性のあるより良い解決策としてプライベートを挙げましたが、元の問題としては述べていません。
Chris Arguin、

@Graeme、そうです、それで「間違いなく」メンバーではありません-しかし、この答えは名前空間メンバーにのみ適用されるステートメントを作成しており、それらのステートメントはメンバー変数に対して間違っています。エラーの数が多すぎると、言語にあまり詳しくない人を混乱させる可能性があるため、修正する必要があります。
Richard Corden

212

多くの人が基本的な答えを出しましたが、C ++ではconstデフォルトstaticnamespaceレベルにあることを誰も指摘しませんでした(そして一部は間違った情報を与えました)。C ++ 98標準セクション3.5.3を参照してください。

最初にいくつかの背景:

翻訳単位:プリプロセッサがすべてのインクルードファイルを(再帰的に)インクルードした後のソースファイル。

静的リンケージ:シンボルは、その翻訳単位内でのみ使用できます。

外部リンケージ:シンボルは他の翻訳単位から利用できます。

で、namespaceレベル

これには、グローバル名前空間またはグローバル変数が含まれます

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

機能レベルで

static関数呼び出し間で値が維持されることを意味します。
関数static変数のセマンティクスは、プログラムのデータセグメント(スタックやヒープではない)に存在するという点でグローバル変数に似ています。この質問を参照してくださいstatic。変数のライフタイムの詳細についてを参照してください。

で、classレベル

static値はクラスのすべてのインスタンス間で共有され、const変更されないことを意味します。


2
関数レベルで:静的のconstとreduntantではない、彼らは異なる動作をすることができますconst int *foo(int x) {const int b=x;return &b};const int *foo(int x) {static const int b=x;return &b};
Hanczar

1
問題はCとC ++の両方に関するconstものstaticであるため、後者についてのみ意味することについてのメモを含める必要があります。
ニコライ・ルーエ

@Motti:すばらしい答えです。関数レベルで何が冗長になるのか説明していただけますか?const宣言staticもそこにあるという意味ですか?のように、キャストしconstて値を変更すると、すべての値が変更されますか?
Cookie

1
@Motti constは関数レベルで静的であることを意味しません。これは同時実行の悪夢(const!=定数式)であり、関数レベルでのすべては暗黙的autoです。この質問にも[c]というタグが付いているので、グローバルレベルconst intは暗黙的externにC であることに言及する必要があります。ただし、ここでのルールはC ++を完全に記述しています。
Ryan Haining

1
C ++では、3つのケースすべてstaticで、変数が静的期間(プログラムの最初から最後まで続くコピーが1つだけ存在する)であり、特に指定されていない場合は内部/静的リンクがあることを示します(これは関数のオーバーライドされます)ローカル静的変数のリンケージ、または静的メンバーのクラスのリンケージ)。主な違いは、staticが有効な各状況でこれが意味することです。
ジャスティンタイム-モニカを

45

そのコード行は実際にはいくつかの異なるコンテキストで表示され、ほぼ同じように動作しますが、小さな違いがあります。

名前空間スコープ

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

' i'は、ヘッダーを含むすべての翻訳単位で表示されます。ただし、実際にオブジェクトのアドレス(たとえば&i、' ')を使用しない限り、コンパイラーは ' i'を単純にタイプセーフとして扱うと確信しています0。さらに2つの変換単位が ' &i'をとる場合、アドレスは変換単位ごとに異なります。

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

' i'には内部リンケージがあるため、この翻訳単位の外部から参照することはできません。ただし、そのアドレスを使用しない限り、ほとんどの場合、タイプセーフとして扱われます0

指摘する価値のあることの1つは、次の宣言です。

const int i1 = 0;

正確に同じstatic const int i = 0。で宣言されconst、明示的に宣言されていない名前空間の変数は、extern暗黙的に静的です。これについて考えると、ODRの破損を避けるためにキーワードをconst常に必要とせずに、ヘッダーファイルで変数を宣言できるようにすることがC ++委員会の意図でしたstatic

クラススコープ

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

上記の例ではi、アドレスが不要な場合は、 ' 'を定義する必要がないことを標準で明示的に指定しています。言い換えるiと、タイプセーフ0として' ' のみを使用する場合、コンパイラーはそれを定義しません。クラスバージョンと名前空間バージョンの1つの違いは、 ' i'(2つ以上の変換ユニットで使用される場合)のアドレスがクラスメンバーで同じになることです。アドレスを使用する場合、その定義が必要です。

// 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
静的constが名前空間スコープのconstと同じであることを示すための+1。
Plumenator

「foo.h」と「foo.cc」の配置に実際の違いはありません。.hは、翻訳単位のコンパイル時に単に含まれるためです。
ミハイル

2
@ミハイル:その通りです。ヘッダーは複数のTUに含めることができるという前提があるため、それについて個別に説明することは役に立ちました。
Richard Corden、2011

24

これは小さなスペースの最適化です。

あなたが言う時

const int foo = 42;

定数を定義するのではなく、読み取り専用変数を作成します。コンパイラーは、fooを検出するたびに42を使用するのに十分なほどスマートですが、初期化されたデータ領域にスペースを割り当てます。これは、定義されているように、fooには外部リンケージがあるためです。別のコンパイル単位は言うことができます:

extern const int foo;

その価値にアクセスするため。そのコンパイル単位はfooの値が何であるかを知らないので、それは良い習慣ではありません。それはそれがconst intであることを知っているだけで、使用されるたびにメモリから値をリロードする必要があります。

今、それが静的であることを宣言することによって:

static const int foo = 42;

コンパイラーは通常の最適化を行うことができますが、「このコンパイルユニットの外部の誰もfooを見ることができず、常に42であるため、スペースを割り当てる必要がないこともわかります」とも言えます。

C ++では、名前が現在のコンパイル単位をエスケープしないようにするための推奨される方法は、匿名の名前空間を使用することです。

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

1
、uは静的を使用せずに「初期化されたデータ領域にもスペースを割り当てます」と述べました。静的な「スペースを割り当てる必要はありません」を使用して(コンパイラーがvalを選択しているところから?)、変数が格納されているヒープとスタックについて説明できます。解釈している場合は修正してください違う 。
Nihar、2015年

@ N.Nihar-静的データ領域は、静的リンケージを持つすべてのデータを含む固定サイズのメモリのチャンクです。プログラムをメモリにロードするプロセスによって「割り当て」られます。スタックやヒープの一部ではありません。
Ferruccio 2015年

fooへのポインタを返す関数があるとどうなりますか?それは最適化を壊しますか?
nw。

@nw:はい、そうする必要があります。
Ferruccio

8

「int」がありません。そのはず:

const static int foo = 42;

CおよびC ++では、ローカルファイルスコープの値が42の整数定数を宣言します。

なぜ42?あなたがまだ知らない場合(そしてあなたが知らないと信じがたい場合)、それはAnswer to Life、Universe、およびEverythingへの参照です。


ありがとう...今はいつも...私の残りの人生... 42を見るとき、私はいつもこれについて考えます。ハハ
イニシア08/10/10

これは、13本の指で宇宙が作成されたことを証明します(質問と回答は実際にはベース13で一致します)。
paxdiablo 2008年

そのマウス。3つの各足のつま先、プラス尾はあなたにベース13与えます
KeithB

宣言に「int」は実際には必要ありませんが、書き出すことは間違いなく良い味です。Cはデフォルトで常に「int」型を想定しています。それを試してみてください!
2008年

「値42のローカルファイルスコープ」?? それともコンパイル単位全体ですか?
aniliitb10

4

C ++では、

static const int foo = 42;

定数を定義および使用するための推奨される方法です。つまり、これを使用するのではなく

#define foo 42

型安全システムを破壊しないためです。


4

すべてのすばらしい答えに、私は少し詳細を追加したいと思います:

プラグイン(たとえば、CADシステムによってロードされるDLLまたは.soライブラリ)を作成する場合、staticは次のような名前の衝突を回避する救命ツールです。

  1. CADシステムは、「const int foo = 42;」のプラグインAをロードします。初期化。
  2. システムは、「const int foo = 23;」のプラグインBをロードします。初期化。
  3. その結果、プラグインBはfooに値42を使用します。これは、プラグインローダーが、外部リンケージを持つ「foo」がすでにあることを認識するためです。

さらに悪いことに:ステップ3は、コンパイラーの最適化、プラグインのロードメカニズムなどによって異なる動作をする場合があります。

2つのプラグインの2つのヘルパー関数(同じ名前、異なる動作)でこの問題が一度発生しました。それらを静的と宣言することで問題は解決しました。


2つのプラグイン間の名前の衝突について何か奇妙に思われ、m_hDfltHeapを外部リンケージ付きのハンドルとして定義する多くのDLLの1つに対するリンクマップを調べました。案の定、リンケージマップに_m_hDfltHeapとしてリストされているのは、世界中が見て使用することです。私はこのファクトイドについてすべて忘れていました。
デビッドA.グレイ

4

C99 / GNU99仕様によると:

  • static

    • ストレージクラス指定子です

    • デフォルトではファイルレベルのスコープのオブジェクトには外部リンケージがあります

    • 静的指定子を持つファイルレベルスコープのオブジェクトには内部リンケージがあります
  • const

    • タイプ修飾子です(タイプの一部です)

    • すぐ左のインスタンスに適用されるキーワード-つまり

      • MyObj const * myVar; -const修飾オブジェクトタイプへの非修飾ポインタ

      • MyObj * const myVar; -非修飾オブジェクトタイプへのconst修飾ポインタ

    • 左端の使用法-変数ではなくオブジェクトタイプに適用されます

      • const MyObj * myVar; -const修飾オブジェクトタイプへの非修飾ポインタ

THUS:

static NSString * const myVar; -内部リンケージを持つ不変文字列への定数ポインター。

不在staticキーワードは、変数名がグローバルになりますと、アプリケーション内で名前の競合を引き起こす可能性があります。


4

C ++ 17 inline変数

「C ++ const static」をグーグル化した場合、これは本当にC ++ 17インライン変数を使用したい可能性が非常に高いです

この素晴らしいC ++ 17機能により、次のことが可能になります。

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

コンパイルして実行:

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アップストリーム

参照:インライン変数はどのように機能しますか?

インライン変数のC ++標準

C ++標準では、アドレスが同じであることを保証しています。C ++ 17 N4659標準ドラフト 10.1.6「インライン指定子」:

6外部リンケージのあるインライン関数または変数は、すべての変換ユニットで同じアドレスを持つ必要があります。

cppreference https://en.cppreference.com/w/cpp/language/inlineは、static指定されていない場合、外部リンクがあることを説明しています

GCCインライン変数の実装

それがどのように実装されているかを観察できます:

nm main.o notmain.o

を含む:

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

man nm言うu

"u"シンボルはユニークなグローバルシンボルです。これは、ELFシンボルバインディングの標準セットに対するGNU拡張です。このようなシンボルの場合、ダイナミックリンカーは、プロセス全体でこの名前とタイプのシンボルが1つだけ使用されていることを確認します。

そのため、専用のELF拡張があることがわかります。

C ++ 17より前: extern const

C ++ 17以前、およびCでは、を使用して非常に類似した効果を実現できますextern const。これにより、単一のメモリロケーションが使用されます。

上の欠点inlineは次のとおりです。

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アップストリーム

C ++ 17以前のヘッダーのみの代替

これらはexternソリューションほど良くありませんが、機能し、メモリの場所を1つしか占有しません。

constexprなぜならこの関数は、constexpr意味inlineinline 、すべての翻訳単位に表示される定義(力を)ことができます

constexpr int shared_inline_constexpr() { return 42; }

まともなコンパイラーも呼び出しをインライン化すると思います。

次のように、constまたはconstexpr静的変数を使用することもできます。

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

しかし、そのアドレスを取得するなどのことはできません。そうしないと、アドレスがodrで使用されます。constexpr静的データメンバーの定義も参照してください。

C

Cでは、状況はC ++ 17より前のC ++と同じです。Cで「静的」とはどういう意味ですか?

唯一の違いは、C ++ではグローバルをconst意味staticしますが、Cでは意味がありません。C++の「静的const」と「const」のセマンティクス

それを完全にインライン化する方法はありますか?

TODO:メモリをまったく使用せずに、変数を完全にインライン化する方法はありますか?

プリプロセッサが行うこととよく似ています。

これはどういうわけか必要になります:

  • 変数のアドレスが取得されるかどうかの禁止または検出
  • その情報をELFオブジェクトファイルに追加し、LTOに最適化させる

関連:

Ubuntu 18.10、GCC 8.2.0でテスト済み。


2

はい、モジュール内の変数を他のモジュールから隠します。C ++では、他のファイルの不要な再構築をトリガーする.hファイルを変更したくない、または変更したくないときに使用します。また、私は静的を最初に置きます:

static const int foo = 42;

また、その使用方法によっては、コンパイラーはストレージを割り当てず、使用される値を単に「インライン化」します。staticなしでは、コンパイラーはそれが他の場所で使用されていないと想定できず、インライン化できません。


2

これは、コンパイルモジュール(.cppファイル)でのみ表示/アクセス可能なグローバル定数です。この目的で静的を使用するBTWは非推奨です。匿名の名前空間と列挙型を使用することをお勧めします:

namespace
{
  enum
  {
     foo = 42
  };
}

これにより、コンパイラーはfooを定数として扱わず、最適化を妨げます。
Nils Pipenbrinck 2008年

列挙値は常に一定なので、これがどのように最適化を妨げるかはわかりません
Roskoto

ああ-true ..私のエラー。単純なint変数を使用したと思った。
Nils Pipenbrinck 2008年

ロスコト、私はenumこの文脈でどのような利益があるのか​​はっきりしていません。詳しく説明しますか?これenumsは通常、コンパイラーが値にスペースを割り当てないようにする(最近のコンパイラーはこのenumハックは必要ない)ため、および値へのポインターの作成を防ぐためにのみ使用されます。
Konrad Rudolph、

コンラッド、この場合、列挙型を使用するときにどのような問題が発生しますか?列挙型は、まさにそうである定数整数が必要な場合に使用されます。
Roskoto 2008年

1

非公開にしても、ヘッダーに表示されます。私は「最も弱い」方法を使用する傾向があります。Scott Meyersによるこの古典的な記事を参照してください:http : //www.ddj.com/cpp/184401197(関数についてですが、ここでも適用できます)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.