組み込みC/C++

C/C++リテラシー向上のためのページ

関数プロトタイプの多重宣言

次のようなプログラムに出くわすことがあります。

 

a.cpp

void g_func(void);
int main(void){
   g_func();
   return 0;
}

 

b.cpp

#include <iostream>
void g_func(void);
void g_func(void){
   std::cout << "文字出力" ;
}

  

どっちにも同じ名前のg_funcというプロトタイプ宣言がいます。a.cppとb.cppのあるプロジェクトをビルドするとどうなるのでしょうか?

これがリンクエラーにならずにビルド出来てしまうのです。

「ちょっと待て他のグローバル関数を引っ張ってくる時はexternを使用するんじゃないのか?」

「この場合はa.cppの関数にextern void g_func(void);としなければいけないのではないか?」

 

考察(仮説):

上のプログラムは下記プログラムと意味合いが一緒です。

 

ab.h

void g_func(void);

 

a.cpp

#include "ab.h"
int main(void){
    g_func();
    return 0;
}

 

b.cpp

#include <iostream>
#include "ab.h" 
void g_func(void){
   std::cout << "文字出力" ;
}
 
上記は正しいヘッダの使い方。
 
プロトタイプ宣言はそもそも
 
「自分のファイルで該当関数の実体より前の位置に関数呼び出しがあれば、引数と戻り値および関数名をコンパイラにお知らせします。」
 
という意味合いでした。実体がそこにいることを保障するものではなくコンパイル時に型を教えるために存在しているのです。
 
また大域領域での関数プロトタイプ宣言はexternal(外部結合)となるので、他のオブジェクトファイル(コンパイル後のファイル)から参照できます。
(Cでは別にプロトタイプ宣言がない場合でも外部ファイルから参照出来てしまいます。そもそも関数の実体を作ることが大域領域に名前と型を登録する事になります。)
 
一方、関数プロトタイプにexternをつけるとどういった意味があるのでしょうか?
 
「extern 指定した関数はどこかのオブジェクトファイルに実体がいるから自分のファイルでこの関数名の呼び出しがあったらそいつはリンカで探して呼びます。」
 
という事であって、コンパイラは関数名をオブジェクトファイルに登録(ラべリング)するだけで、後でリンカでアドレスに置き換える事になります。(なおexternのついた関数宣言は同じファイルにプロトタイプ宣言がいてもエラーをはきません。)
 
まとまりきりませんが、ようするにexternというのは外部に実体がいることを明示するために使用するのであってexternがなくてもプロトタイプ宣言の機能はそもそもexternal(外部参照)だったという事です。
よって文法的な間違いというわけではないのです。リンカは同じ名前の実体があったからそのアドレスを引っ張ってきただけだし、ヘッダにプロトタイプ宣言をする場合とまったく同じ動きになるだけなのです。(むしろヘッダはこういった使い方をするために存在する。)
ただしcpp(もしくはc)ファイル内に関数のプロトタイプ宣言を書くというのは暗にそのファイルに実体がいることを宣言していると考えられるので、呼び出しだけのファイルではexternをつけるのが無難だと思います。
 
結論:
ヘッダでプロトタイプ宣言する癖をつける。