ほっしーの技術ネタ備忘録

技術ネタの備忘録です。基本的に私が忘れないためのものです。他の人の役にも立つといいなぁ。

C言語のリンケージとか変数宣言とか

実はこのあたりの話を理解しないままに10年近くCでコーディングしていたことに気づいたので、勉強したついでに備忘録。

リンケージ

まず基本から。

リンク時にシンボル名が被った時に、実体との対応付けをどうするか、これが3通りあります。

  • 外部リンケージ(ソースファイルを超えて同じ実体を共有)
  • 内部リンケージ(ソースファイルごとに異なる実体を用意、ソースファイル内では同じ実体を共有)
  • リンケージなし(それぞれ別の実体を用意)

局所変数はすべてリンケージなしになります。
グローバル変数は内部/外部リンケージになります。

外部リンケージを持つグローバル変数の宣言・定義

基本形

まず基本的な書き方です。

/* file1.c */
extern int a; /* これは宣言だけ */

/* file2.c */
extern int a; /* こっちのファイルでも宣言だけ */

/* file3.c */
extern int a = 5; /* ここでだけ宣言と定義を行う */

ファイルを超えて共有されるので、リンクオブジェクト全体で1つしか定義してはいけません。

仮定義を用いた場合

さて、ANSI C 以降には仮定義という概念があります。

/* file1.c */
int a; /* 他に a の定義がないので、extern int a = 0; 宣言&定義として振舞う */
/* ※ 初期化子のないグローバル変数は 0 初期化のルール */

/* file2.c */
int a; /* 既に a が定義済みなのでただの extern int a; 宣言として振舞う */

/* file3.c */
int a; /* こちらも同じく extern int a; 宣言として振舞う */

自動的に1つだけ 0 初期化の定義として振舞います。

一般的に使われる形式

グローバル変数を使うときは普段この形で覚えているのではないでしょうか。

/* file1.c */
int a = 3; /* a の宣言と定義 */
/* デフォルトは外部リンケージなので、extern int a; の宣言は省略可能 */

/* file2.c */
extern int a; /* 仮定義じゃないことを明示するため、extern をつけてみる */

恥ずかしながら、私は extern を「定義の抑制」だと思っていました。
そしてそう説明しているページもたくさんあってもう。。。

const のリンケージ

C と違って C++ からは const は自動で内部リンケージを持つようになりました。

// file1.cpp
const int a = 3; /* const に代入はできないので、宣言だけでは使えない */

// file2.cpp
const int a = 3; /* 内部リンケージを持つので、値は同じでも別の実体 */

file1.cpp と file2.cpp をリンクすると、シンボル a の実体、3 はバイナリ内に2か所配置されます。

const シンボルの実体共通化

int ならまだしも、もっと大きなクラスなどの const 実体が複数あるのは無駄ですね。
そこで、これを extern で外部リンケージにすると実体が共通化されます。

// file1.cpp
extern const int a = 3; /* 外部リンケージの const 定義 */

// file2.cpp
extern const int a; /* file1.cpp で定義しているので、こちらの const は宣言だけで済む */

宣言をヘッダファイルにまとめるならば、

// const.h
extern const int a;

// file1.cpp
#include "const.h"
const int a = 3;

// file2.cpp
#include "const.h"

と宣言だけまとめます。

定義もヘッダファイルに書きたければ、gcc 拡張を用いて

// const.h
__attribute__((weak)) extern const int a = 3;

// file1.cpp
#include "const.h"

// file2.cpp
#include "const.h"

とすることも可能ですし、MSVC 拡張で

// const.h
__declspec(selectany) extern const int a = 3;

ともできます。