ソースコードが複数ある場合のモヤモヤを解消 (C++をもう一度)
[履歴] [最終更新] (2016/11/14 01:57:25)
1
作品
364
技術情報
最近の投稿
ここは
趣味の電子工作を楽しむ人のためのハードウェア情報共有サイト

技術情報や作品の投稿機能、リアルタイム遠隔操作 API をご利用いただけます。
新着作品

リンケージ (linkage)

ソースコードが複数ある場合にはリンケージという概念が登場します。関数およびグローバル変数が有する属性で、ファイルを越えて利用できるかどうかを示す性質です。実体が定義されたファイルの外で利用できる関数やグローバル変数を「外部 (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 が指定された変数は内部リンケージを持ちます。これは 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
関連ページ
    静的メンバ変数 sub.h #ifndef SUB_H_ #define SUB_H_ class MyClass { public: MyClass(int intval); public: void Show() const; private: static int m_intval; // 静的メンバ変数 }; #endif // #ifndef SUB_
    概要 POCO (POrtable COmponents) は Boost と同様に有用な C++ のクラスライブラリです。簡単に使用方法を記載します。Boost Software License で配布されていますが Boost への明示的な依存はなく単体で動作します。 A Guided Tour Of The POCO C++ Libraries
    バイナリファイルから文字列を抽出する (strings) 文字列らしい部分をバイナリファイルから抽出して出力するコマンドです。得体の知れないコマンドのオプションを調べたり $ strings /usr/bin/gcc | grep ^-- --help --target-help --sysroot= --all-warnings --ansi --assemble --assert --cl
    ダウンロード こちらから最新のものをダウンロードします。 インストール 解凍します。 $ unzip gtest-1.7.0.zip ビルド時には cmake を利用することが推奨されています。 $ sudo yum install cmake cmake は解凍されたディレクトリの中に含まれるビルドスクリプト CMakeLists.txt をもとにしてビルド用のスクリプトを生成
    概要 cmake の簡単な使い方をまとめます。 参考 URL cmake-buildsystem cmake-commands cmake-variables ごく簡単なcmakeの使い方 公式サンプルコード よく使う周辺コマンド project プロジェクト名を指定します。 project( gtest_main )