暗黙の関数宣言と変数宣言

暗黙の関数宣言

 従来の標準規格では,関数のプロトタイプを宣言しなくても使用することができました.プロトタイプについては連載第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
  $

inline関数定義

 基本的にC++言語の仕様と同じものです.従来の規格で,高速な関数呼び出しを行う場合にはマクロを使用していました.

 しかし,型検査ができなかったり,副作用を起こすといった問題がありました.このC99規格はその問題を解決させるものです.

 ただし,GNU Cではまだ対応できていません.

restrictポインタ

 最適化と効率の良いコードを生成するためには,不可欠な機能です.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
  $

 コンパウンドリテラルを使用したソースで生成したアセンブラでは,引き数が定数になっています.従来の方式で生成したアセンブラでは,スタックに転送しています.

 引き数が定数になっていたほうが,理屈では速いような気がします.

Copyright 2003 岸 哲夫

Copyright 1997-2017 CQ Publishing Co.,Ltd.

 

NEW記事内インデックス    連載インデックスはこちら   Interfaceのトップ
C99規格の予約語について/新しく定義されたマクロとプラグマ/C99規格のマクロ/その他のプリプロセッサ関連
定数の指定方法/配列について/bool型について/long long int型/整数除算に関する規定/複素数型
◆暗黙の関数宣言と変数宣言/定義済みマクロ__func__と可変長引き数マクロ/列挙型について/inline関数定義/restrictポインタ/変数宣言の位置/配列の初期化について/コンパウンドリテラル(Compound Literal)
isblank/関数やマクロの概略