検索
連載

C初心者が知っておきたいヘッダーファイルとリンクの基礎知識目指せ! Cプログラマ(終)(3/4 ページ)

プログラミング言語の基本となる「C」の正しい文法や作法を身に付ける入門連載です。今回は、ヘッダーファイルとリンクを中心に、翻訳単位、ファイル有効範囲、外部定義と仮定義、外部結合と内部結合、結合と記憶域期間、インライン関数の結合、static、extern、inlineなどについても解説。

PC用表示 関連情報
Share
Tweet
LINE
Hatena

結合と記憶域期間

 外部結合あるいは内部結合のどちらでも構いませんが、結合されるオブジェクトは「静的記憶域期間」を持ちます。

 静的記憶域期間とは、「プログラムの実行開始から終了までの期間」のことです。つまり、結合の対象となるオブジェクトは、プログラムが実行されている間は、どの時点でも存在していることになり、「自動記憶域期間」を持つオブジェクトのように途中で消えてしまったりすることはありません。

 例えば、次のプログラムで登場する全てのオブジェクトが静的記憶域期間を持ちます。

int a = 1;        // 外部結合
static int b = 2; // 内部結合
extern int c = 3; // 外部結合
void func(void) {
    extern int b;     // 内部結合
    extern int c;     // 外部結合
    static int d = 4; // 結合は無いが「static」が指定されている
}

 コード例の最後にあるように、ブロック有効範囲で「static」が指定されているオブジェクトは、結合されないことに注意してください。結合はされませんが静的記憶域期間を持ちます。結合されるオブジェクトは静的記憶域期間を持ちますが、静的記憶域期間を持つからといって結合されるわけではありません。

関数の外部結合と内部結合

 関数もオブジェクトと同様に結合が行われます。オブジェクトの外部結合と内部結合を理解していれば簡単です。

#include <stdio.h>
// 外部結合
void func1(void) {
    printf("func1\n");
}
test.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
    void func1(void); // プロトタイプ宣言(外部結合)
    func1();
    return EXIT_SUCCESS;
}
main.c

 「static」を指定すると外部から参照できなくなるのもオブジェクトの結合と同じです。

#include <stdio.h>
// 内部結合
static void func1(void) {
    printf("func1\n");
}
test.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
    // エラー! func1 は内部結合なので参照できない。
    void func1(void); // プロトタイプ宣言
    func1();
    return EXIT_SUCCESS;
}
main.c

 関数の宣言時に「static」が指定されていないときは「extern」が省略されているものとみなされます。もちろん「extern」を書いても構いませんが、書かずに省略する場合が多いようです。「extern」の動作はオブジェクトの場合と同じで、前方に宣言があればそれを参照しますし、そうでなければ外部結合となります。

インライン関数の結合

「inline」が指定されている関数「インライン関数」

 本連載第8回「よく使う処理は関数にしよう」で説明したように、関数には「inline」が指定できます。「inline」が指定されている関数は「インライン関数」と呼ばれ、コンパイラーは「インライン展開」を行う場合があります。

関数呼び出しのコードをその場に展開する「インライン展開」

 インライン展開は、関数呼び出しのコードをその場に展開する機能です。プログラマーからは、さも関数が呼ばれたかのように見えるのでコードの可読性が高まります。この機能を使うと、コンパイラーは関数呼び出しのコストを無くすことができるので、コードを読みやすくしたまま、生成されるプログラムの実行速度を上げることができます。

「static inline」の場合

 「inline」を指定した関数(つまりインライン関数)は、「static」や「extern」が同時に指定されている場合と、そのいずれも指定されていない場合の3パターンがあり、それぞれコンパイラーの解釈が異なります。

 本稿では、これらを「static inline」「extern inline」「inline」と呼ぶことにします。この中で最も自然に理解できるのは「static inline」でしょう。次のコードを見てください。

#include <stdio.h>
#include <stdlib.h>
static inline double half(double arg) {
    return arg / 2.0;
}
int main(void) {
    printf("1 / 2 = %f\n", half(1.0));
    return EXIT_SUCCESS;
}

 関数「half」は「main」内で呼び出されています。この場合、コンパイラーは「half」の呼び出しをインライン展開するでしょう。そしてそれによって「half」の実体は必要なくなるため、関数としての本体は実行コードとして出力されなくなります。

「extern inline」の場合

 「static inline」の代わりに「extern inline」とすると、その関数は外部から参照できるようになります。

#include <stdio.h>
#include <stdlib.h>
extern inline double half(double arg) {
    return arg / 2.0;
}
void test(void);
int main(void) {
    // 可能であればインライン展開される。
    printf("1 / 2 = %f\n", half(1.0));
    
    test();
    return EXIT_SUCCESS;
}
main.c
#include <stdio.h>
double half(double arg);
void test(void) {
    // インライン展開されない。
    // main.c の half を参照する。
    printf("3 / 2 = %f\n", half(3.0));
}
test.c

 「test.c」で定義される関数「half」は「inline」のため、関数「test」内の呼び出しは可能であればインライン展開されます。しかし「extern」も指定されているため外部から呼び出される可能性があります。これらから、「static inline」の場合と違って関数の本体は実行コードに出力されます。

 その結果、「main.c」から普通の関数のように呼び出せます。

単に「inline」と指定したとき

 「static」も「extern」も指定せず、単に「inline」と指定したときは、「インライン定義」になります。これは他の定義(外部定義)とは異なり、関数の本体は出力されません。

#include <stdio.h>
#include <stdlib.h>
inline double half(double arg) {
    return arg / 2.0;
}
void test(void);
int main(void) {
    // 可能であればインライン展開される。
    // インライン展開されない場合は test.c の half を参照する。
    printf("1 / 2 = %f\n", half(1.0));
    
    test();
    return EXIT_SUCCESS;
}
main.c
#include <stdio.h>
inline double half(double arg) {
    return arg / 2.0;
}
//(*)
extern inline double half(double arg);
void test(void) {
    // 可能であればインライン展開される。
    printf("3 / 2 = %f\n", half(3.0));
}
test.c

 インライン定義を使う場合は、プログラム全体のどこかに関数の本体が必要になります。関数の本体は「extern inline」を付けた宣言により定義されます。上記では「test.c」の(*)のすぐ後の宣言がそれに相当しますから、「test.c」をコンパイルしてできる実行コードに関数の本体が出力されます。

 この例では、もしかすると全ての「half」関数の呼び出しがインライン展開されるかもしれません。その場合であっても「extern inline」は必要です。

コラム「インライン関数の互換性」

 「inline」というキーワードはC99で登場しましたが、それ以前から処理系独自の拡張としてインライン展開の実装が行われてきました。それらのインライン展開はそれぞれ独自の仕様を持つため、互換性はありませんでした。また、C99の「inline」とも違う仕様になっていることが多くあります。実際にインラインを指定するときにはコンパイラーの仕様を確認しましょう。

 例えばGCCでは、もともと持っていた「inline」とC99の「inline」と次のような違いがあります。

  • GCCの「static inline」はC99の「static inline」と同じ
  • GCCの「extern inline」はC99の「inline」と同じ
  • GCCの「inline」はC99の「extern inline」と同じ

 GCCでは「-std=c99」を指定するとC99の仕様でコンパイルされます。

 また、インライン定義を使ったときに、全てのインライン関数の呼び出しがインライン展開される場合には外部定義(「extern inline」を指定した宣言)が無くてもコンパイルエラーにならないコンパイラーもあるようです。


Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る