ソースコードが複数ある場合にはリンケージという概念が登場します。関数およびグローバル変数が有する属性で、ファイルを越えて利用できるかどうかを示す性質です。実体が定義されたファイルの外で利用できる関数やグローバル変数を「外部 (external) リンケージをもつ」と表現します。逆に、実体が定義されたファイルの中でのみ利用できる関数やグローバル変数を「内部 (internal) リンケージをもつ」と表現します。
上述の通り、関数やグローバル変数にはリンケージという属性が付与されています。この属性値に関連して、記憶クラス指定子とよばれる static や extern を利用します。関数の場合もグローバル変数の場合も既定値は外部リンケージですが、内部リンケージにするためには static を使用します。外部リンケージをもつ関数やグローバル変数を利用するためには extern を使用します。
sub.h
#ifndef SUB_H_20141218_1351_ // 日付も含めておくと他のヘッダファイルとの衝突可能性を下げられます。
#define SUB_H_20141218_1351_
extern int intval; // 変数のプロトタイプ宣言のようなものです。実体は生成されません。
extern void MyFunc(); // 関数のプロトタイプ宣言
// void MyFunc(); // としても同じです。関数の場合 extern は通常省略します。
#endif // #ifndef SUB_H_20141218_1351_
sub.cpp
#include "sub.h" // https://www.qoosky.io/techs/86f5b4e493 で記載したように
// 自作ヘッダファイルは標準ヘッダファイルより先にインクルード
// します。また、インクルードとはプリプロセッサによる単純な
// 置換であることを意識して理解してください。
#include <iostream>
using namespace std;
int intval = 128; // 実体の生成。extern され得ります。 (既定値は外部リンケージ)
char charval = 'a';
void MyFunc() { // 実体の生成。extern され得ります。 (既定値は外部リンケージ)
cout << "MyFunc" << endl;
}
static int intval_s = 128; // extern をエラーにできます (内部リンケージ化)
static void MyFunc_s() { // extern をエラーにできます (内部リンケージ化)
cout << "MyFunc_s" << endl;
}
main.cpp
#include "sub.h"
#include <iostream>
using namespace std;
int main() {
MyFunc(); //=> "MyFunc"
cout << intval << endl; //=> 128
{
extern char charval; // 様々な場所で extern すなわち
// 「別の場所で定義された外部リンケージのグローバル変数を扱うという宣言」ができます。
cout << charval << endl; //=> a
}
// extern した場所によってそのスコープが決定します。例えば
// cout << charval << endl; // はエラーです。
return 0;
}
Makefile (文法については、こちらを参照してみてください)
CC = g++
CFLAGS = -g -Wall
ALL: main.o sub.o
$(CC) $(CFLAGS) -o main main.o sub.o
main.o: main.cpp
$(CC) $(CFLAGS) -o main.o -c main.cpp
sub.o: sub.cpp sub.h
$(CC) $(CFLAGS) -o sub.o -c sub.cpp
一見すると関係なさそうですが、実は const が指定された変数は内部リンケージを持ちます。これは const 変数の実体生成をヘッダファイルで行えることを意味しています。同様の理由で、static 変数についてもヘッダファイルでの実体生成が文法上は可能です。配列のサイズなどを指定するのに便利な const 変数はヘッダファイルで実体生成されることが多いです。その際、二重インクルードには注意しましょう。繰り返しますが、以下のサンプルを理解するにあたり #include インクルードとはプリプロセッサによるファイルの埋め込みであることに注意してください。
sub.h
#ifndef SUB_H_20141218_1351_
#define SUB_H_20141218_1351_
// 外部リンケージをもつ、別の場所で定義されたグローバル変数を利用します宣言
extern int intval;
// 内部リンケージの変数実体を生成
const int INT_VAL = 128;
// const 変数と同様に static 変数も内部リンケージですので
// ヘッダファイルで実体を生成してもリンク時に二重定義エラーは発生しません。
static char charval_s = 'a';
// しかしながら、外部リンケージの変数について
// 二つのファイルで実体が生成されると、リンク時に二重定義エラーが発生します。
// char charval = 'b'; // ← リンク時にエラー
#endif // #ifndef SUB_H_20141218_1351_
sub.cpp
#include "sub.h"
int intval = 128;
main.cpp
#include "sub.h"
#include <iostream>
using namespace std;
int main() {
cout << intval << endl; //=> 128
cout << INT_VAL << endl; //=> 128
return 0;
}
const と同様にこちらも一見すると関係なさそうですが、実は無名名前空間の内部で定義された関数や変数は内部リンケージを持ちます。内部的にはファイルごとに「異なる」無名名前空間が作られるためです。名前空間についてはこちらを参照してください。
sub.cpp
#include <iostream>
using namespace std;
// グローバル領域における内部リンケージ (sub.cpp内からのみ利用可能)
namespace {
void _MyFunc() {
cout << "::_MyFunc" << endl;
}
}
namespace MyName {
// MyName名前空間内における内部リンケージ (sub.cpp内からのみ利用可能)
namespace {
void _MyFunc() {
cout << "MyName::_MyFunc" << endl;
}
}
// 外部リンケージをもつ関数
void MyFunc() {
::_MyFunc();
_MyFunc();
}
}
// 外部リンケージをもつ関数
void MyFunc() {
_MyFunc();
MyName::_MyFunc();
}
main.cpp
#include <iostream>
using namespace std;
extern void MyFunc(); // 関数のexternは通常省略します (前述)
// extern void _MyFunc(); // ← 内部リンケージを持つため参照できません
namespace MyName {
extern void MyFunc(); // 関数のexternは通常省略します (前述)
// extern void _MyFunc(); // ← 内部リンケージを持つため参照できません
}
int main() {
MyFunc(); //=> ::_MyFunc
// MyName::_MyFunc
MyName::MyFunc(); //=> ::_MyFunc
// MyName::_MyFunc
return 0;
}
Makefile
CC = g++
CFLAGS = -g -Wall
ALL: main.o sub.o
$(CC) $(CFLAGS) -o main main.o sub.o
main.o: main.cpp
$(CC) $(CFLAGS) -o main.o -c main.cpp
sub.o: sub.cpp
$(CC) $(CFLAGS) -o sub.o -c sub.cpp
「静的」とはプログラムが実行される前に不確定要素がすべてなくなっていることを意味します。静的でないものを「動的」と表現します。「静的変数」とはプログラムが実行される前にメモリ領域が確保され、実行中にそのメモリアドレスが一定であるような変数です。具体的にはグローバル変数や static が付与されたローカル変数が該当します。「動的変数」とはプログラムが実行される時点ではメモリ領域が確保されておらず、実行中にそのメモリ領域の確保とアドレスの決定がなされる変数です。具体的には static が付与されていないローカル変数が該当します。自動変数ともよばれます。静的変数はその性質から想像のつくように最初に一回だけ初期化処理が行われます。その際、明示的な初期値が与えられていなければ 0 で初期化されるという仕様になっています。
#include <iostream>
using namespace std;
int intval = 128; // 静的かつ外部リンケージ (重要: グローバル変数は static を付与せずとも静的変数です)
// int intval; // ← とすると 0 で初期化
static int intval_s = 128; // 静的かつ内部リンケージ
void MyFunc() {
static int myfunc_intval_s = 0; // 静的 (かつNoリンケージ、つまり
// ローカル変数にはリンケージという概念がない)
// static int myfunc_intval_s; // としてもよい (0 初期化)
cout << myfunc_intval_s << endl;
++myfunc_intval_s;
}
int main() {
cout << intval << endl; //=> 128
MyFunc(); //=> 0
MyFunc(); //=> 1
return 0;
}
ソースコードをコンパイルするとオブジェクトファイルが生成され、生成されたオブジェクトファイルとライブラリをリンクすることで実行ファイルを生成します。このときのリンクを特に静的リンクとよびます。上述の通り「静的」とはプログラムが実行される前に不確定要素がすべてなくなっていることを意味します。ライブラリにも静的なものと動的なものがあり、前者すなわちオブジェクトファイルの集合体のようなものは静的リンクして実行ファイルに埋め込みます。一方、後者の動的なものは静的ライブラリと実行ファイルの中間的存在であり、プログラムの実行中にリンクして使用されます。これを特に動的リンクとよびます。
UNIX 系 OS の ".a" (archive) ファイルは静的ライブラリで ".so" (shared object) ファイルは動的ライブラリです。動的ライブラリは、通常 /lib や /usr/lib に配置されています。Windows における ".dll" (dynamic link library) ファイルも動的ライブラリです。ldd コマンドによって使用する動的ライブラリ一覧を取得できます。
$ sudo ldd /bin/bash
linux-vdso.so.1 => (0x00007fff07fc2000)
libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007fa658494000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fa658290000)
libc.so.6 => /lib64/libc.so.6 (0x00007fa657efb000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa6586bb000)
動的リンクが可能なライブラリは /lib64
, /lib
, /usr/lib64
, /usr/lib
に加えて、LD_LIBRARY_PATH
で指定されたパスおよび /etc/ld.so.conf
で指定されたパスです。手動で /etc/ld.so.conf
を更新して ldconfig
コマンドを実行すると /etc/ld.so.cache
が更新されます。conf ファイルは人間が編集するためのファイルであって cache を更新しなければならないことを忘れないようにします。
sudo vim /etc/ld.so.conf
sudo ldconfig
以下のコマンドで動的リンク可能なライブラリ一覧が表示できます。
ldconfig -p
詳細表示のためには以下のコマンドを実行します。
sudo ldconfig -v