ローカルに宣言されたラベル

 ローカルラベル機能が役に立つのは,複文式〔小括弧()内に中括弧{}で文をまとめたもの〕がマクロの中でしばしば使われるからです.あるマクロの中に入れ子になったループがあると,そのループから抜け出るのにgotoが役に立ちます.ただし,その場合,スコープが関数全体である通常のラベルを使うことはできません.

 なぜなら,そのマクロが一つの関数の中で複数回展開されることがあると,同じ関数の中でそのラベルが複数回定義されることになるからです.局所ラベルはこのような問題を回避します.

 GCCの標準機能で実装するには,ラベルを使用する命令を使わないことです.プログラムは冗長になるかもしれませんが,可読性は高まります.

値としてのラベル

 これは昔からメインフレームのCOBOLやアセンブラやROM BASICでコードを小さくするために使用されてきた「飛び先テーブル」の一種です.

 これを使用すると読みにくく,バグを出したときに見つけにくいので使用しないほうがよいと思います.

 そのラベルがスコープ内にある関数の中で定義されたラベルのアドレスを,単項演算子&&で獲得することができます.この値の型はvoid *です.この値は定数であり,void *型の定数が正当であるところではどこでも使うことができます.次に例を示します.

  static void *array[]
= { &&foo, &&bar, &&hack };

 こうすると,以下のようにインデックスを使ってラベルを選択することができます.

  goto *array[i];

 ここで,配列の添字が上限内にあるかどうかチェックされないことに注意してください.Cにおける配列のインデックスでは,このようなチェックは決して行われません.

 このようなことを行うのならば,switch文のほうが,よりすっきりしています.実現しようとしていることがswitch文にうまく適合しない場合以外は,ラベル値の配列ではなく,switch文を使うようにしてください.

入れ子になった関数

 C言語の拡張機能でネストされた関数を作ることができます(リスト5).C++言語では使えません.

〔リスト5〕ネストされた関数
void foo( double x[], double y[], int num )
{
  int i;
  int X( double xd )
 {
    return( (int)(2.0*xd) );
  }
  int Y( double yd )
 {
    return( (int)(4.0*yd) );
  }
			
  for( i=0 ; i<num ; i++ )
 {
    printf("%d %d\n", X(x[i]), Y(y[i]) );
  }
}

 入れ子関数が定義された箇所において可視な変数は,すべて入れ子関数の中からアクセスすることができます.

 関数の中で変数定義ができるところであればどこでも,入れ子関数の定義を行うことができます.つまり,任意のブロック内で,そのブロックの最初にある文の前であれば,入れ子関数の定義が可能です.

 入れ子関数のアドレスをどこかに格納したり,別の関数へ渡すことによって,入れ子関数の名前が有効なスコープの外部からでも,入れ子関数を呼び出すことができます(リスト6).

〔リスト6〕入れ子関数の呼び出し
test (int *array, int size)
{
  void store (int index, int value)
    { array[index] = value; }
			
  test1 (store, size);
}

 リスト6の関数test1はstoreのアドレスを引き数として受け取っています.test1がstoreを呼び出すと,storeに渡された引き数はarrayへの値の格納に使われます.しかし,このテクニックは,入れ子関数を包含している関数(この例ではtest)が終了しないかぎりにおいてのみ有効です.

 かりに関数test1内部でどこかにstoreのアドレスを保存しても,関数testが終了した後,そこを参照しても不定です.確実にコアダンプしてしまうでしょう.

 GCCの標準機能で同等の機能を実装することはできません.可読性の点から,あまり使わないほうが無難です.

関数呼び出しを構築する

 以下で説明する組み込み関数を使うことで,ある関数の引き数の数や型がわからなくても,その関数が受け取った引き数を記録して,別の関数を同じ引き数で呼び出すことができます.

 また,その関数が返そうとした戻り値のデータ型がわからなくても,その関数呼び出しの戻り値を記録して,後にその値を返すことができます.

○__builtin_apply_args ()

 この組み込み関数は,現在処理中の関数を呼び出すためのデータを返します.関数は,スタックに割り当てられたメモリのブロックへ引き数を関数へ渡すために使われる引き数,ポインタ,レジスタ,構造体値アドレスとすべてのレジスタを保存します.その後,そのデータブロックのアドレスをvoidポインタとして返します.

○__builtin_apply (function, arguments, size)

 この組み込み関数は,引き数(void*型)とサイズ(int型)によって記述されたパラメータのコピーで関数〔void(*)()型〕を起動します.引き数の値は上の__builtin_apply_argsによって返された値でなければなりません.引き数サイズはバイトで,スタック引き数データのサイズを指定します.

 この関数は,対象の関数が戻す値のデータを返します.

 データは,スタックに割り当てられたメモリのブロックで保存されます.その後,そのデータブロックのアドレスをvoidポインタとして返します.

○__builtin_return (result)

 この組み込み関数は,resultの値を戻り値として,この組み込み関数を実行している関数から復帰します.resultには,__builtin_applyから返された値を指定しなければなりません.

 GCCの標準機能で同等の機能を実装することはできません.

型の代入

 初期化子(initializer)とともにtypedef宣言を使うことで,式の型に名前を与えることができます.以下に,式expの型の名前としてnameを定義する方法を示します.

  typedef name = exp;

 この機能を使うと先に使ったマクロで,型にとらわれないものが作成できます(リスト7).

〔リスト7〕マクロに型の代入を使ったソースtest46.c)
#include <stdio.h>
#define max_a(a,b) ({ typedef _ta = (a), _tb = (b);_ta _a = (a); _tb _b = (b); _a > _b ? _a : _b; } )
int main(void)
{
 int a,b,c,ans;
 long la,lb,lc,lans;
 a = 2;
 b = 10;
 c = 8; 
 la = 2000000;
 lb = 1000000;
 lc = 8000000; 
 ans = c * max_a(a, b); 
 printf("正しい答え(拡張機能) = %d\n", ans); 
 lans = lc * max_a(la, lb); 
 printf("正しい答え(拡張機能) = %d\n", lans); 
 return;
}

  #define max_a(X,Y) \
  ({typedef _X = (X), _Y = (Y); _X > _Y ? _X : _Y; })

 これで文字列以外の比較ができるようになります.実行結果を以下に示します.

  $ ./test46
  正しい答え(拡張機能) = 80
  正しい答え(拡張機能) = 1246822400
  $

 long値でも正しい結果になりました.GCCの標準機能で同等の機能を実装することはできません.

Copyright 2003 岸 哲夫

Copyright 1997-2017 CQ Publishing Co.,Ltd.

 

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