記事には広告を含む場合があります。

C言語入門講座

【C/C++】externとは?関数・変数・配列・構造体との使い方

おゆ

組み込み系プログラマとして5年働いていた元エンジニアです。得意言語はC言語とC++。本サイトでは学生および新人組み込み系プログラマに向けてプログラミング知識をわかりやすく解説しています。

複数のソースファイルで共通のグローバル変数を使いたい時に利用されるのがexternです。

通常、変数は宣言と定義が同時に行われますが、externを使うと定義は行わずに宣言だけを行えます。

externとは何か?なぜ必要か?わかりやすく解説

複数ファイルで同じグローバル変数を使いたいときにはextern宣言を使います。

extern宣言を使わないとあるソースファイルで定義したグローバル変数を別のソースファイルでも使うということができません。

コンパイルはファイル単位で行われるので、何も書かないと他のファイルでどんな変数が定義されているのかわからないためです。

コンパイルすると1つのソースファイルあたり1つのオブジェクトファイルが作られます。このオブジェクトファイルをすべて統合(リンク)することでプログラムが作られます。

例えば以下のプログラムはsub.cでNUMの値が不明なのでコンパイルエラーになります。

例1:

main.c

#include <stdio.h>
#include "head.h"
int NUM=0; //グローバル変数の宣言・定義・初期化
void main()
{
    NUM=NUM+1;
    print_NUM(); //NUMの値を出力する関数
}

head.h

#ifndef HEAD_H //インクルードガード
#define HEAD_H

void print_NUM();

#endif

sub.c

#include <stdio.h>
#include "head.h"
void print_NUM(){printf("NUM=%d\n",NUM);}

実行結果

(sub.cにおいてNUMの値が不明でコンパイルエラー)

参考【C/C++】ファイル分割(ヘッダファイルの作り方、変数・構造体の書き方)

プログラムが大きくなると1つのファイルだけでは管理しにくくなります。 そんなときはソースファイルを関連する機能別にファイルに分割して保存すると管理が楽です。 このファイル分割時に作るのがヘッダファイル ...

続きを見る

ヘッダファイルにNUMの定義を書けばいいんじゃないの?
おゆ
やってみましょうか。

例2:

main.c

#include <stdio.h>
#include "head.h"
void main()
{
    NUM=0; //グローバル変数の初期化
    NUM=NUM+1;
    print_NUM(); //NUMの値を出力する関数
}

head.h

#ifndef HEAD_H //インクルードガード
#define HEAD_H

int NUM; //グローバル変数の宣言・定義
void print_NUM();

#endif

sub.c

#include <stdio.h>
#include "head.h"
void print_NUM(){printf("NUM=%d\n",NUM);}

実行結果

(NUMが複数回定義されているのでリンクエラー)

変数の定義を複数回やるとエラーになるんだ…でもインクルードガードをしているのに何でNUMの定義を複数回やってることになるの?
おゆ
ちょっと余談になりますが、複数定義のエラーになるかどうかは「定義が実際に何回実行されるか」でなく、「定義が複数のソースファイルに書かれている(2回以上実行される可能性がある)」ことが問題だからです。

インクルードガードが書いてあったとしてもコンパイラは「if文の結果によってはNUMの定義が実行されるかもしれないからNUMの定義は残しておこう」と判断し、コンパイルの結果としてできるオブジェクトファイルにNUMの定義を書いておきます。

例2では2つのソースファイルでヘッダファイルをインクルードしているので、リンク(オブジェクトファイルを統合して1つのプログラムとする操作)時にNUMの定義が被っいるのが発見されてエラーになります。

複数のソースファイルで共通のグローバル変数を使いたい場合は次のようにextern宣言を使えば正しいコードになります。

例3:

main.c

#include <stdio.h>
#include "head.h"
int NUM=0; //グローバル変数の定義・初期化
void main()
{
    NUM=NUM+1;
    print_NUM(); //NUMの値を出力する関数
}

head.h

#ifndef HEAD_H //インクルードガード
#define HEAD_H

extern int NUM; //グローバル変数のextern宣言
void print_NUM();

#endif

sub.c

#include <stdio.h>
#include "head.h"
void print_NUM(){printf("NUM=%d\n",NUM);}

実行結果

NUM=1

同じグローバル変数の定義は複数回行うとエラーになりますが、宣言は何回行っても問題ないので正常にコンパイルできます。

externとは「このファイルではこの変数を使いますよ(宣言)。定義は別ファイルで行いますよ。」とコンパイラに伝えるための機能です

externを使えば複数ファイルで同じグローバル変数を使いまわせるようになります。

グローバル変数におけるexternの使い方

externを使うにあたっては宣言と定義の違いを知っておく必要があります。

宣言と定義の違い

  • 宣言とはコンパイラに変数や関数などの型と名前を知らせること。何回書いてもOK。
    (存在を知らせるだけで変数などを作るわけではない)
  • 定義とはメインメモリ上に変数や関数などの領域を割り当てること。1つのプログラムで1度しか書けない。

変数は一般的に宣言と定義を同時に行います。

例えば「int NUM;」のように書くと「NUMという整数型変数を使いますよ」と宣言すると同時に「NUM用の領域をメインメモリに確保してください」という定義も同時に行っています。

C言語、C++において宣言は複数回行っても良いですが、定義は一度しか行ってはいけないという決まりがあります

そのため複数のファイルで「int NUM;」と書いてしまうと定義が複数回行われているという理由でエラーになります。

ここまで読んだ皆さんはこう思うのではないでしょうか。

定義はせずに宣言だけできないのかなあ…

そんな希望を叶えるのがextern宣言です。

extern宣言をすれば変数の宣言だけができるんです。

externの使い方

extern データ型 変数名;

1つのソースファイルで変数定義(externなし)を行い、同じ変数を使いたい別ファイルでextern宣言します。

extern宣言と同時に変数の初期化はできません。

一般的にはヘッダファイルにグローバル変数のextern宣言を書き、ソースファイルのどれか1つに定義と初期化を書いて使います。

例3(再掲):

main.c

#include <stdio.h>
#include "head.h"
int NUM=0; //グローバル変数の定義・初期化
void main()
{
    NUM=NUM+1;
    print_NUM(); //NUMの値を出力する関数
}

head.h

#ifndef HEAD_H //インクルードガード
#define HEAD_H

extern int NUM; //グローバル変数のextern宣言
void print_NUM();

#endif

sub.c

#include <stdio.h>
#include "head.h"
void print_NUM(){printf("NUM=%d\n",NUM);}

実行結果

NUM=1

extern使用時の注意点

乱用厳禁

extern宣言は何度書いてもエラーにはなりませんが、とりあえず書いておくのはNGです。

必ず必要なファイルにのみ書くようにしてください

どのファイルでもグローバル変数が使えるようになっていると思わぬタイミングで値が書き換えられ、意図しない値を使ってしまうミスが起こりやすいです。

グローバル変数が使える範囲は必要最低限にしておくのが定石です。

extern宣言とは別に定義を書く

extern宣言だけを書いて定義を書かないと「定義がありません」というエラーが出ます。

宣言はコンパイラに「こういう変数を作りますよ」と知らせるだけで、実体を作る(メインメモリに変数用の領域を確保する)わけではありません。

実体を作る命令をするには定義を書く必要があります。

定義を書くと同時に宣言もしたことになるので、定義を書いたファイルではextern宣言する必要はありません。

extern宣言時に初期化はできない

「extern int NUM=1;」のようにextern宣言と同時に初期化することはできません。

初期化するということは変数定義をして(=メインメモリに領域を確保して)そこに値を格納するということに等しいです。

しかしextern宣言はあくまで宣言のみをするためにあるので、同時に定義・初期化はできません。

変数に値を代入したいのなら定義時に初期化も行うか、定義後に代入式を書きます。

配列におけるexternの使い方

配列のグローバル変数に対してextern宣言するコードは以下のように書けます。

例4:

main.c

#include <stdio.h>
#include "head.h"
void main()
{
    printf("ARRAY=%d,%d,%d\n",ARRAY[0],ARRAY[1],ARRAY[2]);
}

head.h

#ifndef HEAD_H
#define HEAD_H

extern int ARRAY[]; //配列のextern宣言(要素数未定義でもOK)

#endif

sub.c

#include "head.h" 
int ARRAY[]={1,2,3}; //配列の宣言・定義・初期化

実行結果

ARRAY=1,2,3

ただしこのコードでsizeof演算子を使って配列ARRAYの要素数を取得しようとすると「要素数未定義の配列のサイズは取得できません」というエラーが出ます。

extern宣言時に要素数が確定していないと未定義として扱われてしまうのです。

配列サイズを使用したい場合は以下のようにextern宣言時に要素数を指定しておきます。

例5:

main.c

#include <stdio.h>
#include "head.h"
void main()
{
    printf("ARRAY=%d,%d,%d\n",ARRAY[0],ARRAY[1],ARRAY[2]);
    printf("ARRAY size=%d\n",sizeof(ARRAY)/sizeof(ARRAY[0]));
}

head.h

#ifndef HEAD_H
#define HEAD_H

#define NUM_SIZE 3 //配列サイズをマクロで定義
extern int ARRAY[NUM_SIZE]; //配列のextern宣言

#endif

sub.c

#include "head.h" 
int ARRAY[]={1,2,3}; //配列の宣言・定義・初期化

実行結果

ARRAY=1,2,3
ARRAY size=3

配列の要素数は「extern int ARRAY[3];」というように定数で指定してもいいのですが、上記のように変数にした方がより一般化できて応用が利きます。

関数のプロトタイプ宣言にはexternを使わない

ヘッダファイルには関数のプロトタイプ宣言も書くんだけどそっちはextern宣言にしなくていいの?
おゆ
プロトタイプ宣言にはexternは不要です。

externは定義を行わずに宣言だけを行いたいときに付けるんでしたよね。

ですが関数のプロトタイプ宣言は宣言だけを行うことが明確なのでextern宣言する必要がありません。

externを付けても付けなくても同じなので、通常は付けません。

構造体におけるexternの使い方

構造体の宣言にはexternを付ける必要はありません。

構造体の定義をヘッダファイルで行って同じ構造体変数を使い回す場合にはextern宣言を使います。

例6:

main.c

#include <stdio.h>
#include "head.h"
void main()
{
    printf("ID=%d, Name=%s\n",sato.id,sato.name);
    print_person();
}

head.h

#ifndef HEAD_H
#define HEAD_H

//構造体の宣言
typedef struct
{
    int id;
    char name[30];
}person;
//構造体変数のextern宣言
extern person sato;

//関数のプロトタイプ宣言
void print_person();

#endif

sub.c

#include <stdio.h>
#include "head.h"

//構造体変数の定義
person sato={0,"Sato Hiroyuki"};

//関数の定義
void print_person()
{
    printf("ID=%d, Name=%s\n",sato.id,sato.name);
}

構造体変数はグローバル変数と同じように宣言・定義できます。

C言語で行き詰まったら…

C言語・C++がわからない時に質問できるサイト・サービス5選

C言語を独学で勉強しているけど内容がイマイチわからない。もはや何がわからないのかもわからない。 C++のエラーが解決できない。ググってもわからない。わかる人に解説してほしい。 こんなお悩みにお答えしま ...

続きを見る

ブログランキング参加中。クリックしてもらえると励みになります。

ブログランキング・にほんブログ村へ

-C言語入門講座