C言語におけるGCCの拡張機能

 ここではC言語に関することだけ扱います.C++,Objective Cに関しては,回を改めて説明することにします.

 GNUのCには,ANSI規格にしたがっていない拡張機能がいくつかあります.たしかに便利なのですが,それに慣れてしまうと他の環境でプログラムが書けなくなってしまう恐れがあります.また,他の環境にポーティングすることが難しくなってしまいます.

 しかし,用途によっては拡張機能によってわかりやすくコーディングすることが必要かもしれません.したがって,どのように使用するかで拡張機能を排除するか許可するかを考えてください.

 もし排除する場合は,連載第3回目(2002年10月号)に記したように,-pedanticオプションを使用します.

 このオプションは厳密なANSI CおよびISO C++により要求される警告をすべて出力します.禁止されている拡張機能を使うプログラムをすべて拒絶します.正当なANSI Cプログラム,およびISO C++プログラムであれば,このオプションの指定の有無にかかわらず正しくコンパイルされるはずです.しかし,このオプションが指定されないと,特定のGNU拡張機能もまたサポートされることになります.

 もし,拡張機能を使用してコードを書いた場合,条件コンパイルでANSI,非ANSIを分けることが可能です.これらの機能が利用可能であるかどうかをテストするためには,__GNUC__というマクロが事前に定義されているかどうかをチェックします.この__GNUC__というマクロは,GCCでは常に定義されています.

 LinuxのGUI開発環境として話題のC++言語版のKylixでは,GCCの独自拡張機能を使う場合,新たに環境の設定をしなくてはなりません.もし,Kylixとの互換を取るならば,ANSI準拠にしなくてはなりません.

 今回と次回の2回にわたり,この拡張機能の説明と検証を行います.

式の中の文と宣言

 拡張機能として,小括弧()内に中括弧{}でまとめた複文を入れることができます.この複文中ではループや変数宣言もできます.最後の項がやはり小括弧()の値を表します.

 引き数のうち大きなほうを戻すマクロmax_a()を,次のように作ることができます.

  #define max_a(X,Y) \

    ({int _X = (X), _Y = (Y); _X > _Y ? _X : _Y; })

 引き数の値を一時的な変数に保管し,それの大小を評価しているので,「マクロの副作用」にはまることはありません.

 GCCの標準機能で実装するには,inline関数を使うことです.そのほうが確実で簡単かつ安全です(リスト1リスト2).

〔リスト1〕副作用が発生するマクロ,および拡張機能を使ったマクロを使用したCソースtest44.c)
#include <stdio.h>
#define max(X, Y) X > Y ? X : Y 
#define max_a(X,Y) \
 ({int _X = (X), _Y = (Y); _X > _Y ? _X : _Y; })
int main(void)
{
 int a,b,c,ans;
 a = 2;
 b = 10;
 c = 8; 
 ans = c * max(a, b); 
 printf("副作用が発生した答え = %d\n", ans); 
 ans = c * max_a(a, b); 
 printf("正しい答え(拡張機能) = %d\n", ans); 
 return;
}

〔リスト2〕リスト1に加えてinline関数を使用したCソースtest45.c)
#include <stdio.h>
#define max(X, Y) X > Y ? X : Y 
#define max_a(X,Y) \
 ({int _X = (X), _Y = (Y); _X > _Y ? _X : _Y; })
static __inline__ int max_i(int X,int Y);
int main(void)
{
 int a,b,c,ans;
 a = 2;
 b = 10;
 c = 8; 
 ans = c * max(a, b); 
 printf("副作用が発生した答え = %d\n", ans); 
 ans = c * max_a(a, b); 
 printf("正しい答え(拡張機能) = %d\n", ans); 
 ans = c * ( max_i(a, b) ); 
 printf("正しい答え1(インライン関数) = %d\n", ans); 
 ans = c * max_i(a, b) * 2; 
 printf("正しい答え2(インライン関数) = %d\n", ans); 
 return;
}
static __inline__ int max_i(int X,int Y)
{
 return X > Y ? X : Y;
}

 実行結果は以下のようになります.

  $ ./test44
  副作用が発生した答え = 2
  正しい答え(拡張機能) = 80
  $ ./test45
  副作用が発生した答え = 2
  正しい答え(拡張機能) = 80
  正しい答え1(インライン関数) = 80
  正しい答え2(インライン関数) = 160
  $

 リスト3にtest44.cのアセンブラリストを,リスト4にtest44.cのアセンブラリストを示します.

 リストからわかるように,ここではinline関数を指定しても展開されません.C++の処理を行っていないからです.

 リスト1の二つのマクロは,以下のように展開されます.

  ans = c * a > b ? a : b ;

  ans = c * ({int _X = ( a ), _Y = ( b ); _X > _Y ? _X : _Y; })

 上の式では問題が起きることがわかると思います.

〔リスト3〕test44.cのアセンブラリストtest44.s)
 .file "test44.c"
 .version "01.01"
gcc2_compiled.:
.section .rodata
.LC0:
 .string "\311\373\272\356\315\321\244\254\310\257\300\270\244\267\244\277\305\372\244\250 = %d\n"
.LC1:
 .string "\300\265\244\267\244\244\305\372\244\250\241\312\263\310\304\245\265\241\307\275\241\313 = %d\n"
.text
 .align 4
.globl main
 .type  main,@function
main:
 pushl %ebp
 movl %esp,%ebp
 subl $40,%esp
 movl $2,-4(%ebp)
 movl $10,-8(%ebp)
 movl $8,-12(%ebp)
 movl -12(%ebp),%eax
 imull -4(%ebp),%eax
 cmpl -8(%ebp),%eax
 jle .L3
 movl -4(%ebp),%eax
 jmp .L4
 .p2align 4,,7
.L3:
 movl -8(%ebp),%eax
.L4:
 movl %eax,-16(%ebp)
 addl $-8,%esp
 movl -16(%ebp),%eax
 pushl %eax
 pushl $.LC0
 call printf
 addl $16,%esp
 movl -4(%ebp),%eax
 movl %eax,-20(%ebp)
 movl -8(%ebp),%eax
 movl %eax,-24(%ebp)
 movl -24(%ebp),%eax
 cmpl -20(%ebp),%eax
 jge .L5
 movl -20(%ebp),%eax
.L5:
 imull -12(%ebp),%eax
 movl %eax,-16(%ebp)
 addl $-8,%esp
 movl -16(%ebp),%eax
 pushl %eax
 pushl $.LC1
 call printf
 addl $16,%esp
 jmp .L2
 .p2align 4,,7
.L2:
 movl %ebp,%esp
 popl %ebp
 ret
.Lfe1:
 .size  main,.Lfe1-main
 .ident "GCC: (GNU) 2.95.3 20010315 (release)"
〔リスト4〕test45.cのアセンブラリストtest45.s)
 .file "test45.c"
 .version "01.01"
gcc2_compiled.:
.section .rodata
.LC0:
 .string "\311\373\272\356\315\321\244\254\310\257\300\270\244\267\244\277\305\372\244\250 = %d\n"
.LC1:
 .string "\300\265\244\267\244\244\305\372\244\250\241\312\263\310\304\245\265\241\307\275\241\313 = %d\n"
 .align 32
.LC2:
 .string 

"\300\265\244\267\244\244\305\372\244\2501\241\312\245\244\245\363\245\351\245\244\245\363\264\330\277\364\241\313 =  %d\n"
 .align 32
.LC3:
 .string 

"\300\265\244\267\244\244\305\372\244\2502\241\312\245\244\245\363\245\351\245\244\245\363\264\330\277\364\241\313 =  %d\n"
.text
 .align 4
.globl main
 .type  main,@function
main:
 pushl %ebp
 movl %esp,%ebp
 subl $8,%esp
 addl $-8,%esp
 pushl $2
 pushl $.LC0
 call printf
 addl $16,%esp
 addl $-8,%esp
 pushl $80
 pushl $.LC1
 call printf
 addl $-8,%esp
 pushl $10
 pushl $2
 call max_i
 sall $3,%eax
 addl $32,%esp
 addl $-8,%esp
 pushl %eax
 pushl $.LC2
 call printf
 addl $-8,%esp
 pushl $10
 pushl $2
 call max_i
 sall $4,%eax
 addl $32,%esp
 addl $-8,%esp
 pushl %eax
 pushl $.LC3
 call printf
 movl %ebp,%esp
 popl %ebp
 ret
.Lfe1:
 .size  main,.Lfe1-main
 .align 4
 .type  max_i,@function
max_i:
 pushl %ebp
 movl %esp,%ebp
 movl 8(%ebp),%edx
 movl 12(%ebp),%eax
 cmpl %edx,%eax
 jge .L22
 movl %edx,%eax
.L22:
 movl %ebp,%esp
 popl %ebp
 ret
.Lfe2:
 .size  max_i,.Lfe2-max_i
 .ident "GCC: (GNU) 2.95.3 20010315 (release)"

Copyright 2003 岸 哲夫

Copyright 1997-2017 CQ Publishing Co.,Ltd.

 

NEW記事内インデックス    連載インデックスはこちら   Interfaceのトップ
GCCの導入に際して
◆C言語におけるGCCの拡張機能
 式の中の文と宣言
 ローカルに宣言されたラベル/値としてのラベル/入れ子になった関数/
  関数呼び出しを構築する /型の代入
 typeofで型を参照する/拡張されたlvalue