前回に続いてGNU Cの拡張機能について詳細に説明と検証を行います.
● プロトタイプ宣言と古い方式の関数定義
通常,GNU Cでコードを書く場合に,関数定義のプロトタイプ宣言を省略する人はいないと思います.省略した場合には以下のような問題が発生します.
・使用する関数は使用される前に定義されていなければならない
関数が数十数百ある場合にそんなことを考えるのは不毛です.
・関数に渡す引き数が正しくなくてもコンパイルが通ってしまう
一見矛盾なく動いてしまいますが,後々おかしなことになります.
・戻り値の型を間違えて戻してもエラーにならない
charだと思っていた戻り値が,じつはlongで,しかもその値を別の関数の呼び出しに使って落ちた……などという場合には,かなり見つけにくいバグになるでしょう.
連載第5回(本誌2003年1月号)で詳細に記してありますが,もしプロトタイプ宣言をしていない古いソースを使わなければならない場合は,protoizeコマンドを使用するなどして関数定義のプロトタイプ宣言を付加するべきだと思います.
リスト1〜リスト4の例では,関数の型の暗黙の型宣言がなされてしまい,結果としてまったく違う値を返してしまっています.
まずプロトタイプを使用しない場合の結果は,次のようになります.
$ gcc -o test83 test83.c
test83.c:13: warning: type mismatch with previous implicit declaration
test83.c:7: warning: previous implicit declaration of 'Double2'
test83.c:13: warning: 'Double2' was previously implicitly declared to return 'int'
$ ./test83
start
6.000000
$
プロトタイプを使用した結果は,次のようになります.
$ gcc -o test84 test84.c
$ ./test84
start
4.000000
$
この例では意図的にエラーとなる場合を挙げていますが,実際にこのようなことが数多く起こりえます.
test83ではプロトタイプを使用しないため,関数の型に対し暗黙の型変換がなされてしまい,double Double2(double result)という関数がint Double2(int result)とコンパイラに解釈されてしまったのです.アセンブラを見るとわかるように引き数も,戻り値も間違っています.
プロトタイプの前置きが長くなりましたが,本題です.
古いソースにprotoizeを使用してプロトタイプを付加したとしても関数の定義が古いままだと,ANSI Cの場合,不具合が発生することがあります.GNU Cの拡張機能としてリスト5とリスト6の二つのコードが同等になります.
〔リスト5〕関数定義が古いままのソース(test85.c)
|
#include <stdio.h>
typedef short t_id;
int foo(t_id x);
int main(void)
{
int result;
t_id y;
printf("start\n");
y = 2;
result = foo(y);
printf("%d\n",result);
return 0;
}
int foo(x)
t_id x;
{
return x * x;
}
|
|
〔リスト6〕関数定義を修正したソース(test87.c)
|
#include <stdio.h>
typedef short t_id;
int foo(t_id x);
int main(void)
{
int result;
t_id y;
printf("start\n");
y = 2;
result = foo(y);
printf("%d\n",result);
return 0;
}
int foo(t_id x)
{
return x * x;
}
|
|
拡張機能を使わない方法でtest85.cをコンパイルした結果は,次のようになります.
$ gcc -pedantic test85.c -o test85
test85.c:1: warning: carriage return in preprocessing directive
test85.c:2: warning: carriage return in source file
test85.c:2: warning: (we only warn about the first carriage return)
test85.c: In function 'foo':
test85.c:17: warning: promoted argument 'x' doesn't match prototype
test85.c:3: warning: prototype declaration
$
拡張機能を使った場合,次のようになります.
$ gcc test85.c -o test85
$
拡張機能を使わない場合,関数fooの引き数の型が暗黙にintとなっています.プロトタイプ宣言で指定した型が間違っているとワーニングメッセージが出てしまいます.
拡張機能を使用した場合,正しく認識されています.つまり,プロトタイプ宣言のほうが正しいとみなしてコンパイルをしています.
この機能を使用する場合は,ANSIでコンパイルしないようにコメントなどを記述し,明示すべきです.便利な機能ですが,可読性に問題が発生するでしょう.
|