第8回 C言語におけるGCCの拡張機能(3)

岸 哲夫

 今回は,前回に引き続きGNU Cで使用できる拡張機能について説明と検証を行う.拡張機能には,使用することにより効率的なコードを生成できるもの,簡潔な表現が行えるものなども多い.しかし,それらを使用することによる可搬性の低下なども起こりうる.これらについて,実際にコンパイルを行った結果を見ながら,解説を行う. (編集部)

space

 前回に続いて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でコンパイルしないようにコメントなどを記述し,明示すべきです.便利な機能ですが,可読性に問題が発生するでしょう.


NEW記事内インデックス    連載インデックスはこちら   Interfaceのトップ
◆プロトタイプ宣言と古い方式の関数定義
コメントの形式/識別子の名前の中のドル記号/ESC文字について/型あるいは変数のアラインメントを問い合わせる/変数の属性の指定
型属性の指定/Cの式をオペランドとしてもつアセンブラ命令
アセンブラコードの中で使われる名前の制御/指定されたレジスタの中の変数/代替キーワード/不完全なenum型/関数名の文字列/関数の復帰アドレスとフレームアドレスの獲得

Copyright 2003 岸 哲夫

Copyright 1997-2017 CQ Publishing Co.,Ltd.