万悪の源:C言語における暗黙関数宣言

6964 ワード

1 C言語の暗黙関数宣言とは
C言語では、関数を呼び出す前に宣言する必要はありません.宣言がない場合、コンパイラは自動的に暗黙的に宣言されたルールに従って、関数を呼び出すCコードにアセンブリコードを生成します.次に例を示します.
int main(int argc, char** argv)
{
    double x = any_name_function();
    return 0;
}

上記のソースコードを単純にコンパイルしても、何の間違いもありませんが、リンク段階でanyという名前が見つからないためです.name_functionの関数体が間違っています.
[smstong@centos192 test]$ gcc -c main.c
[smstong@centos192 test]$ gcc main.o
main.o: In function `main':
main.c:(.text+0x15): undefined reference to `any_name_function'
collect2: ld    1

コンパイルが間違っていないのは、C言語で宣言されていない関数に対して、自動的に暗黙的な宣言が使用されることが規定されているからです.次のコードに相当します.
int any_name_function();
int main(int argc, char** argv)
{
    double x = any_name_function();
    return 0;
}

2もたらす問題
2.1暗黙宣言関数名はリンクライブラリに適切に存在するが、int以外のタイプを返す
前述の例では、リンクフェーズで問題が発見されやすいため、あまり影響を及ぼさない.しかし、次の例では、なぜか実行時エラーが発生します.
#include 
int main(int argc, char** argv)
{
    double x = sqrt(1);
    printf("%lf", x);
    return 0;
}

gccコンパイルリンク
[smstong@centos192 test]$ gcc -c main.c
main.c:    ‘main’ :
main.c:6:   :         ‘sqrt’   
[smstong@centos192 test]$ gcc main.o

実行結果
1.000000

コンパイル時に警告が表示され、暗黙的な宣言が組み込み関数'sqrt'と互換性がないことを示します.gccコンパイラは、コンパイル時によく使用されるライブラリヘッダファイル(組み込み関数)で暗黙的に宣言された関数と同じ名前の関数を自動的に検索できます.両者が異なる場合、組み込み関数の宣言プロトタイプに従って呼び出しコードを生成します.これは、プログラマーが予想した考えでもあります.上記の例で暗黙的に宣言された関数プロトタイプは、次のようになります.
int sqrt(int);

対応する同名の組み込み関数のプロトタイプは、次のとおりです.
double sqrt(double);

最終コンパイラは組み込み関数のプロトタイプに従ってコンパイルされ,所期の効果を達成した.しかし、gccコンパイラのこのような行為はC言語の規範ではなく、すべてのコンパイラ実装にこのような機能があるわけではない.同じソースコードをVC++2015でコンパイルして実行した結果は:
VC++コンパイル
warning C4013: “sqrt”   ;       int

実行結果
2884223.000000

明らかに、VC++コンパイラにはいわゆる「組み込み関数」はないが、暗黙的に宣言されたプロトタイプに従って、sqrt関数を呼び出すコードを生成するだけだ.戻りタイプとパラメータタイプが異なるため、誤った関数呼び出し方式が発生し、奇妙なランタイムエラーが発生します.
この場合、戻りタイプによっては、両方のコンパイラが警告情報を与えることができ、少なくともプログラマーの注意を引き起こすことができます.次のような状況は、より隠れています.
2.2暗黙宣言関数名がリンクライブラリに適切に存在し、intタイプを返す
テストコードは次のとおりです.
#include 

int main(int argc, char** argv)
{
    int x = abs(-1);
    printf("%d", x);
    return 0;
}

この場合,暗黙的に宣言された関数プロトタイプはgccの内蔵関数プロトタイプと全く同じであるため,gccは何の警告も与えず,結果も正しい.VC++は、warning C 4013:absが定義されていないと警告します.外部がintを返すと仮定します.
いずれにしても、暗黙的に宣言された関数のプロトタイプはライブラリ関数と全く同じなので、リンクの実行には問題ありません.
次に、コードを少し変更します.
#include 

int main(int argc, char** argv)
{
    int x = abs(-1,2,3,4);
    printf("%d", x);
    return 0;
}

gccの下でリンクをコンパイルしてもエラーはありません.
gccコンパイルリンク
[smstong@centos192 test]$ gcc -c main.c
[smstong@centos192 test]$ gcc main.o

gccの組み込み関数メカニズムは関数のパラメータに関心を持たず,関数の戻り値に関心を持つことが分かった.
vc++コンパイルリンク
warning C4013: “abs”   ;       int

この例の実行結果はすべて正しいが、追加の関数パラメータが結果に影響を与えないため、この正確さは「たまたま」である.このような偶然が正しいのはプログラムの中で避けなければならない.
3プログラミングにおける注意事項
C言語の暗黙関数宣言は,プログラマーに様々な困惑をもたらし,プログラムの安定性に非常に悪い影響を及ぼした.当初、C言語設計者はこの問題をどのように考えていたのか分からない.
*このような影響を回避するために、プログラマはコンパイラから与えられた暗黙的な宣言に関する警告を重視し、必要なヘッダファイルを含むことで警告を排除することを強くお勧めします.*
gccにとって,前述したabs(−1,2,3,4)の特殊な例では,コンパイラは警告をまったく生じず,プログラマが自分で呼び出したライブラリ関数を熟知するしかない.
このような問題を回避するために、C言語のC 99バージョンでは、どうしても警告が出ます.gccがC 99を使用して上記のコードをコンパイルするようにします.
gcc-std=c 99コンパイル
[smstong@centos192 test]$ gcc -c main.c -std=c99
main.c:    ‘main’ :
main.c:5:   :      ‘abs’

C++はより厳格で、暗黙的な関数宣言を直接捨て、未宣言の関数の呼び出しに対してはコンパイルできません.
g++コンパイル
[smstong@centos192 test]$ g++ main.c
main.c: In functionint main(int, char**)’:
main.c:5:   :‘abs’          

vc++コンパイル(C++として)
error C3861: “abs”:       

関数の強いタイプという点では,C++は確かにCよりも厳格で厳密である.