モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

ソースコードが複数ある場合のモヤモヤを解消 (C++をもう一度)

モーダルを閉じる

ステッカーを選択してください

お支払い手続きへ
モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2014/12/19
最終更新最終更新
2019/02/18
記事区分記事区分
一般公開

目次

    Spring Bootでの実用的な機能をわかりやすく解説中

    リンケージ (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" 
                     // 自作ヘッダファイルは標準ヘッダファイルより先にインクルード
                     // します。また、インクルードとはプリプロセッサによる単純な
                     // 置換であることを意識して理解してください。
    #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
    
    Likeボタン(off)0
    詳細設定を開く/閉じる
    アカウント プロフィール画像

    Spring Bootでの実用的な機能をわかりやすく解説中

    記事の執筆者にステッカーを贈る

    有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

    >>さらに詳しくステッカーを贈る
    ステッカーを贈る コンセプト画像

    Feedbacks

    Feedbacks コンセプト画像

      ログインするとコメントを投稿できます。

      ログインする

      関連記事