ここでは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)"
|
|
|