● 暗黙の関数宣言
従来の標準規格では,関数のプロトタイプを宣言しなくても使用することができました.プロトタイプについては連載第8回でも触れていますが,重要な問題です.
エラーにならなくてもプロトタイプ宣言すべきだと思います.
● 暗黙の変数宣言
従来の標準規格では,
const x = 10;
のような使い方ができました.
この場合,xはint型になりますが,C99規格ではこのような使用法はできないことになりました.しかし,GNU Cではまだできています.
以下に,リスト14のコンパイルと実行結果を示します.
〔リスト14〕暗黙の変数宣言(test118.c)
|
/*
*暗黙の型
*/
#include <complex.h>
main()
{
const x = 1;
printf("%d\n",x);
}
|
|
$ gcc test118.c -o test118
$ ./test118
1
$ gcc -ansi -pedantic test118.c -o test118
$ ./test118
1
$
どちらの方法でもエラーになりません.
定義済みマクロ__func__と可変長引き数マクロ
|
リスト15のようなソースなら絶対に追えますが,初心者に作られたスパゲッティプログラムを追うのは嫌なものです.そんな場合,関数内に定義済みマクロ__func__を表示するprintfを入れると,非常にわかりやすくなって,スパゲッティプログラムをデバッグするのが楽しくなります(?).
この機能は,GNU Cの拡張機能として以前から使われていましたが,C99規格にも取り入れられました.
以下に,リスト15のコンパイルおよび実行結果を示します.-ansiオプションを使用するとエラーになります.
〔リスト15〕定義済みマクロ__func__を使ったデバッグ(test119.c)
|
/*
*デバッグに役立つ定義済みマクロ
*/
#define dbg(...) (printf("%s %u @%s:",__FILE__,__LINE__,__func__),printf(" "__VA_ARGS__))
#include <complex.h>
void foo1(int i);
void foo2(int i);
void foo3(int i);
void foo4(int i);
main()
{
int x = 1;
foo1(x++);
foo2(x++);
foo3(x++);
foo4(x);
}
void foo1(int i)
{
dbg("i=%d\n", i);
}
void foo2(int i)
{
dbg("i=%d\n", i);
}
void foo3(int i)
{
dbg("i=%d\n", i);
}
void foo4(int i)
{
dbg("i=%d\n", i);
}
|
|
$ gcc test119.c -o test119
$ ./test119
test119.c 20 @foo1: i=1
test119.c 24 @foo2: i=2
test119.c 28 @foo3: i=3
test119.c 32 @foo4: i=4
$
$ gcc -ansi -pedantic test119.c -o test119
test119.c:4: warning: invalid character in macro parameter name
test119.c:4: badly punctuated parameter list in `#define'
リスト16のアセンブラソースを見てわかるとおり,各関数の最初でマクロの処理をしています.
〔リスト16〕生成されたアセンブラソース(test119.s)
|
.file "test119.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $1,-4(%ebp)
addl $-12,%esp
movl -4(%ebp),%eax
pushl %eax
incl -4(%ebp)
call foo1
addl $16,%esp
addl $-12,%esp
movl -4(%ebp),%eax
pushl %eax
incl -4(%ebp)
call foo2
addl $16,%esp
addl $-12,%esp
movl -4(%ebp),%eax
pushl %eax
incl -4(%ebp)
call foo3
addl $16,%esp
addl $-12,%esp
movl -4(%ebp),%eax
pushl %eax
call foo4
addl $16,%esp
.L2:
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.section .rodata
.LC0:
.string "foo1"
.LC1:
.string "test119.c"
.LC2:
.string "%s %u @%s:"
.LC3:
.string " i=%d\n"
.text
.align 4
.globl foo1
.type foo1,@function
foo1:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
pushl $.LC0
pushl $20
pushl $.LC1
pushl $.LC2
call printf
addl $16,%esp
addl $-8,%esp
movl 8(%ebp),%eax
pushl %eax
pushl $.LC3
call printf
addl $16,%esp
.L3:
movl %ebp,%esp
popl %ebp
ret
.Lfe2:
.size foo1,.Lfe2-foo1
.section .rodata
.LC4:
.string "foo2"
.text
.align 4
.globl foo2
.type foo2,@function
foo2:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
pushl $.LC4
pushl $24
pushl $.LC1
pushl $.LC2
call printf
addl $16,%esp
addl $-8,%esp
movl 8(%ebp),%eax
pushl %eax
pushl $.LC3
call printf
addl $16,%esp
.L4:
movl %ebp,%esp
popl %ebp
ret
.Lfe3:
.size foo2,.Lfe3-foo2
.section .rodata
.LC5:
.string "foo3"
.text
.align 4
.globl foo3
.type foo3,@function
foo3:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
pushl $.LC5
pushl $28
pushl $.LC1
pushl $.LC2
call printf
addl $16,%esp
addl $-8,%esp
movl 8(%ebp),%eax
pushl %eax
pushl $.LC3
call printf
addl $16,%esp
.L5:
movl %ebp,%esp
popl %ebp
ret
.Lfe4:
.size foo3,.Lfe4-foo3
.section .rodata
.LC6:
.string "foo4"
.text
.align 4
.globl foo4
.type foo4,@function
foo4:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
pushl $.LC6
pushl $32
pushl $.LC1
pushl $.LC2
call printf
addl $16,%esp
addl $-8,%esp
movl 8(%ebp),%eax
pushl %eax
pushl $.LC3
call printf
addl $16,%esp
.L6:
movl %ebp,%esp
popl %ebp
ret
.Lfe5:
.size foo4,.Lfe5-foo4
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
従来の規格では,ソース例のようにenumの項目に余分なカンマがあると,ワーニングを出しましたが,C99規格ではそれを許しています.よって,-ansiオプションを使用するとワーニングになります.
リスト17のコンパイルと実行結果を下記に示します.また,生成されたアセンブラをリスト18に示します.
〔リスト17〕列挙型(test120.c)
|
/*
*列挙型
*/
#include <stdio.h>
enum cafe
{
grande,
tall,
small,
};
main()
{
enum cafe JAVA;
JAVA = tall;
printf("%d\n",JAVA);
}
|
|
〔リスト18〕生成されたアセンブラソース(test120.s)
|
.file "test120.c"
.version "01.01"
gcc2_compiled.:
.section .rodata
.LC0:
.string "%d\n"
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
addl $-8,%esp
pushl $0
pushl $.LC0
call printf
addl $16,%esp
.L2:
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
$ gcc -ansi -pedantic test120.c -o test120
test120.c:10: warning: comma at end of enumerator list
$ gcc test120.c -o test120
$ ./test120
1
$
基本的にC++言語の仕様と同じものです.従来の規格で,高速な関数呼び出しを行う場合にはマクロを使用していました.
しかし,型検査ができなかったり,副作用を起こすといった問題がありました.このC99規格はその問題を解決させるものです.
ただし,GNU Cではまだ対応できていません.
最適化と効率の良いコードを生成するためには,不可欠な機能です.C99規格の目玉の一つでしょう.
簡単にいえば,「そのポインタ変数は同一の番地を絶対に指していない」とコンパイラに教えるためにあります.たとえば,その関数内で二つのポインタ変数が別の番地を指して,どちらもメモリ内部の値を更新していると仮定します.一つ目のポインタ変数が00030210番地を更新した後,二つ目のポインタがある番地を更新する際に00030210番地の内容が必要なとき,00030210番地を更新した値をそのまま使ったほうが効率がよいはずです.
問題は,二つのポインタが同じ番地を指していたら,お互いに干渉し,その値が不定かもしれないという点です.
そんなとき,コンパイラに「そのポインタ変数は同一の番地を絶対に指していない」と教えることで,効率のよいコードが作成できます.
これはGNU Cでも対応しています.
C++では,変数の宣言を使用する行で行っても問題はありません.
しかし,従来の規格ではグローバル変数は関数を定義する直前までに,また関数内部で使用する局所変数は,そのコードのブロックの先頭で行わなければなりませんでした.
C99規格では,変数の宣言の位置に関してはC++風になりました.
GNU Cではまだ未対応です.
リスト19のようなコードの場合,コンパイル時に以下のようなことになります.
〔リスト19〕変数宣言の位置(1)(test121.c)
|
/*
*変数の宣言場所(1)
*/
#include <stdio.h>
main()
{
char buf1[10240];
memset(&buf1,0,sizeof(buf1));
int i = 1;
for (i=0;i<sizeof(buf1);i++)
{
buf1[i] = i;
}
}
|
|
$ gcc -S test121.c
test121.c: In function `main':
test121.c:9: parse error before `int'
test121.c:10: `i' undeclared (first use in this function)
test121.c:10: (Each undeclared identifier is reported only once
test121.c:10: for each function it appears in.)
$
また,リスト20のようなコードでは,次のような結果になります.
〔リスト20〕変数宣言の位置(2)(test122.c)
|
/*
*変数の宣言場所(2)
*/
#include <stdio.h>
main()
{
int i = 1;
char buf1[10240];
memset(&buf1,0,sizeof(buf1));
for (int i=0;i<sizeof(buf1);i++)
{
buf1[i] = i;
}
}
|
|
$ gcc -S test122.c
test122.c: In function `main':
test122.c:10: parse error before `int'
test122.c:10: parse error before `)'
$
C99規格では配列を初期化する際に,配列の要素やメンバ名を指定することで,わかりやすく初期化を行えます.連載第7回,「拡張機能」の中で配列の初期化について例を挙げて解説してあるので参考にしてください.
GNU Cでは,拡張機能として実装されていました.非常に可読性が高まるので,使ってみてください.
コンパウンドリテラル
(Compound Literal) |
構造体を引き数として関数に渡したいときに,構造体を確保して,変数をセットして……としなくてもできるようになりました.
もちろん,すべてこのようにしたら可読性に問題が出ますが,デバッグなどの際には非常に便利だと思います.
GNU Cも対応しています.
コンパウンドリテラルを使った例をリスト21に,その結果を下記に示します.また,生成されたアセンブラソースをリスト22に示します.
〔リスト21〕コンパウンドリテラル(test123.c)
|
/*
*Compound Literal
*/
#include <stdio.h>
typedef struct { int x, y; } XY_struct ;
int test(const XY_struct *p);
main()
{
int res;
res = test(&(XY_struct){100, 200});
printf("%d\n",res);
res = test(&(XY_struct){500, 235});
printf("%d\n",res);
}
int test(const XY_struct *p)
{
return (p->x * p->y);
}
|
|
〔リスト22〕生成されたアセンブラソース(test123.s)
|
.file "test123.c"
.version "01.01"
gcc2_compiled.:
.section .rodata
.align 4
.LC0:
.long 100
.long 200
.LC1:
.string "%d\n"
.align 4
.LC2:
.long 500
.long 235
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $24,%esp
addl $-12,%esp
pushl $.LC0
call test
addl $16,%esp
movl %eax,%eax
movl %eax,-4(%ebp)
addl $-8,%esp
movl -4(%ebp),%eax
pushl %eax
pushl $.LC1
call printf
addl $16,%esp
addl $-12,%esp
pushl $.LC2
call test
addl $16,%esp
movl %eax,%eax
movl %eax,-4(%ebp)
addl $-8,%esp
movl -4(%ebp),%eax
pushl %eax
pushl $.LC1
call printf
addl $16,%esp
.L2:
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.align 4
.globl test
.type test,@function
test:
pushl %ebp
movl %esp,%ebp
movl 8(%ebp),%eax
movl 8(%ebp),%ecx
movl (%eax),%edx
imull 4(%ecx),%edx
movl %edx,%eax
jmp .L3
.p2align 4,,7
.L3:
movl %ebp,%esp
popl %ebp
ret
.Lfe2:
.size test,.Lfe2-test
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
$ gcc test123.c -o test123
$ ./test123
20000
117500
$
また,コンパウンドリテラルを使用せず,従来の方法で行った例をリスト23に示します.実行結果を以下に,生成されたアセンブラソースをリスト24に示します.
〔リスト23〕同じことを通常の方法で(test124.c)
|
/*
*Compound Literal
*/
#include <stdio.h>
typedef struct { int x, y; } XY_struct ;
int test(const XY_struct *p);
main()
{
int res;
XY_struct var1;
var1.x = 100;
var1.y = 200;
res = test(&var1);
printf("%d\n",res);
var1.x = 500;
var1.y = 235;
res = test(&var1);
printf("%d\n",res);
}
int test(const XY_struct *p)
{
return (p->x * p->y);
}
|
|
〔リスト24〕生成されたアセンブラソース(test124.s)
|
.file "test124.c"
.version "01.01"
gcc2_compiled.:
.section .rodata
.LC0:
.string "%d\n"
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $100,-12(%ebp)
movl $200,-8(%ebp)
addl $-12,%esp
leal -12(%ebp),%eax
pushl %eax
call test
addl $16,%esp
movl %eax,%eax
movl %eax,-4(%ebp)
addl $-8,%esp
movl -4(%ebp),%eax
pushl %eax
pushl $.LC0
call printf
addl $16,%esp
movl $500,-12(%ebp)
movl $235,-8(%ebp)
addl $-12,%esp
leal -12(%ebp),%eax
pushl %eax
call test
addl $16,%esp
movl %eax,%eax
movl %eax,-4(%ebp)
addl $-8,%esp
movl -4(%ebp),%eax
pushl %eax
pushl $.LC0
call printf
addl $16,%esp
.L2:
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.align 4
.globl test
.type test,@function
test:
pushl %ebp
movl %esp,%ebp
movl 8(%ebp),%eax
movl 8(%ebp),%ecx
movl (%eax),%edx
imull 4(%ecx),%edx
movl %edx,%eax
jmp .L3
.p2align 4,,7
.L3:
movl %ebp,%esp
popl %ebp
ret
.Lfe2:
.size test,.Lfe2-test
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
$ gcc test124.c -o test124
$ ./test124
20000
117500
$
コンパウンドリテラルを使用したソースで生成したアセンブラでは,引き数が定数になっています.従来の方式で生成したアセンブラでは,スタックに転送しています.
引き数が定数になっていたほうが,理屈では速いような気がします.
|