gcc(Gnu C Compiler)の拡張文法

[警告!] C/C++言語初心者はこのページを読まないでください。

このページではgcc独自のC/C++拡張文法について解説します。 これらの拡張文法が可能にする機構は確かに便利なのですが、 もちろんANSI規格に従っていないので、一般的には使うべきではありません。

C/C++言語文法を学び始めている初心者はこれらgcc拡張文法を 知るべきではありません。C/C++言語を正しく理解する上で大きな 支障となります。
C/C++言語を十分に熟知した者は、gccがこのようなこともすることを 「雑談」として知っておくと楽しいかもしれません。もちろん 実戦に使うべきではありませんが。しかし初心者が偶然に、これらの 機能を使ってうまくいく場合がありますので、そのような初心者を 見つけたら、それが標準規格ではないことを注意してください。


  • 配列変数をコピーする。

    gccでは同型で同じサイズの配列変数間に限り、 次のようにして配列変数の全要素をコピーすることができます。

    int a[8], b[8]="StringB";
    a = b;                      // GCC Extension?
    
    しかし gccの提供するinfoにはこの機能についての説明がありません。


  • 変数で配列変数を初期化する。

    標準規格では配列の初期化で指定できる値は定数ですが、
    gcc拡張文法では変数を指定することができます。
    int a,b;に対して次のように配列変数を初期化することができます。

    int n[3] = { a+b, a-b, a*b };            // GCC Extension
    
    標準規格で実装するには、宣言後に個別に代入することです。


  • 配列のサイズを変数で指定する。

    標準規格では配列の宣言時に与える配列のサイズは定数でですが、
    gcc拡張文法では変数でサイズを指定することができます。
    次のように配列のサイズを実行時に決定できます。

    int n;
    scanf("%d", &n );
    int a[n];                   // GCC Extension
    
    標準規格で実装するには、newを使うことです。


  • switch caseで値の範囲を指定する。

    標準規格ではswitch case文のcaseに指定する値はただ一つですが、
    gcc拡張文法ではPascalのように値の範囲を指定することができます。
    int n;に対して次のようにswitch case文を記述することができます。

    switch( n ){
      case 0 ... 4 : /* do something */ break;   // GCC Extension
      case 5 ... 9 : /* do something */ break;   // GCC Extension
    }
    
    標準規格で実装するには、if文を多用することです。


  • アドレス値の足し算、引き算をする。

    標準規格ではアドレス値は足し引き算の対象ではありませんが、
    gcc拡張文法では一般化された左辺値の機能とキャスト演算子を 使ってアドレス値の足し引き算をすることができます。
    char* ptr; int add;に対して次のようにアドレス値の足し算が できます。

    (int)ptr += add;        // GCC Extension
    
    標準規格で実装するには、
    (int)(ptr = (char *)(int) ((int)ptr + add));
    
    とすることです。


  • 小括弧()内の複文で一時的な変数を宣言して使う。

    標準規格では小括弧()内に単純な式をカンマで並べて、その最後の項が 小括弧()の値を表しますが、
    gcc拡張文法では小括弧()内に中括弧{}でまとめた複文を入れることができます。 この複文中ではループや変数宣言もできます。最後の項がやはり小括弧() の値を表します。
    max()の#defineマクロを次のように作ることができます。

    #define max(a,b)   (      \
    {                         \      // GCC Extension
      int _a = (a), _b = (b); \
      _a > _b ? _a : _b;      \
    }                         \      // GCC Extension
    )
    
    引数の値を一時的な変数に保管して、それの大小を評価しているので、 よくある#defineマクロの落し穴に落ちません。

    標準規格で実装するには、inline関数を使うことです。 そのほうが遥かに簡単で安全です。


  • 型を代入する。

    gcc拡張文法では typedef で型を表す変数のようなものを作ることができます。
    次のようにすると、ty型は変数xと同じ型(つまりint型)になり、 その型で変数yが宣言されます。

    int x;
    typedef ty = x;      // GCC Extension
    ty y;                // GCC Extension
    
    この機能を先のmax()の#defineマクロに利用すると有用でしょう。
    #define max(a,b) (               \
    {                                \   // GCC Extension
      typedef _ta = (a), _tb = (b);  \   // GCC Extension
      _ta _a = (a); _tb _b = (b);    \   // GCC Extension
      _a > _b ? _a : _b;             \
    }                                \   // GCC Extension
    )
    
    これで任意の型の変数同士のmax()マクロができます。 (>オペレータが適切に定義されているとして。)

    標準規格で実装するには、templateを次のように使うことです。

    template <class T> T max( T a, T b ){
      return (a>b) ? a : b;
    }
    
    ただしこれはinline展開できません。


  • 変数の型を表す。

    gcc拡張文法ではtypeof演算子で変数の型を表すことができ、 さらにそれで別の変数を宣言することができます。
    次のようにすると、変数yは変数xの型で宣言されます。

    int x;
    typeof(x) y;            // GCC Extension
    
    この機能を先のmax()の#defineマクロに利用すると有用でしょう。
    #define max(a,b) (               \
    {                                \      // GCC Extension
      typeof(a) _a;                  \      // GCC Extension
      typeof(b) _b;                  \      // GCC Extension
      _a = (a); _b = (b);            \
      _a > _b ? _a : _b;             \
    }                                \      // GCC Extension
    )
    

    標準規格で実装するには、先のようにtemplateを使うことです。


  • 大きい方、小さい方を表す演算子を使う。

    gcc拡張文法でのC++では特別な演算子 >? と <? が使えます。 >? は a >? b として、大きい方を表し、 <? は a <? b として、小さい方を表します。
    汎用のmax()マクロとmin()マクロは次のように作られます。

    #define max(a,b) ((a) >? (b))       // GCC Extension of C++
    #define min(a,b) ((a) <? (b))       // GCC Extension of C++
    
    #defineマクロの落し穴に落ちることもありません。

    標準規格で実装するには、やはりtemplateです。


  • 条件演算子の中間項を省略する。

    標準規格では条件演算子の文法は

    条件式 ? 真の場合の式 : 偽の場合の式
    ですが、
    gcc拡張文法では「真の場合の式」を省略でき、真の場合には条件式の 値が「真の場合の式の値」となります。
    fopen()関数が返す値は、正常でFILE型のアドレスで、失敗でNULLで、 NULLが0であり条件式が偽になることを利用して次のような 関数を作ることができます。
    FILE* myfopen( char* fname )
    {
      return fopen(fname,"r") ? : (perror("fopen"),(FILE*)NULL);   // GCC Extension
    }
    
    正常ならFILEポインタを返して、失敗ならperror()関数で 失敗の原因を報告してからNULLポインタを返します。

    標準規格で実装するには、一時的なFILEポインタ変数を 用意することです。


  • goto文に使うラベルの有効範囲を複文内にする。

    標準規格ではラベルの有効範囲はその関数内全域ですが、
    gcc拡張文法では次のようにしてラベルを宣言することで、有効範囲を その宣言を囲む中括弧内に限定することができます。

    // GCC Extension
    __label__ retry;
    
    #defineマクロ内でgoto文を使う場合に、この限定されたラベルが 必要です。なぜなら同じ関数内でそのマクロを複数回呼び出すと、 標準規格ではそのラベルが衝突するからです。
    次のようなマクロを作ることができます。
    #define INPUT()  (              \
    {                               \    // GCC Extension
      __label__ retry;              \    // GCC Extension
      int _x;                       \
     retry:                         \
      printf("Input an integer=");  \
      if( scanf("%d", &x ) != 1 ){  \
        rewind(stdin);              \
        goto retry;                 \
      }                             \
      x;                            \
    }                               \    // GCC Extension
    )
    
    標準規格で実装するには、inline関数を使うことです。


  • ラベルを変数に格納してgotoで使う。

    gcc拡張文法ではラベルとgoto文を次のように使うことができます。

    void dispatch( int i )
    {
      static void *array[] = { &&job0, &&job1, &&job2 };   // GCC Extension
      goto *array[i];                                      // GCC Extension
    
     job0:
      printf("doing the job0.\n");
      return;
    
     job1:
      printf("doing the job1.\n");
      return;
    
     job2:
      printf("doing the job2.\n");
      return;
    }
    
    N88BASICの ON GOTO文のようなことができるわけです。 switchによる分岐と異なり変数の評価が無いので分岐が高速です。

    標準規格で実装するには、ラベルの代わりに関数ポインタ を関数ポインタ配列に入れておいて、gotoの代わりにその関数ポインタ 配列の要素が指す関数を実行することです。


  • 関数の中に関数を作る。

    gcc拡張文法でのC言語ではネストされた関数を作ることができます。 C++言語では使えません。

    void plot( double x[], double y[], int num )
    {
      int i;
      int X( double xd ){              /* GCC Extension of C */
        return( (int)(2.0*xd) );
      }
      int Y( double yd ){              /* GCC Extension of C */
        return( (int)(4.0*yd) );
      }
    
      for( i=0 ; i<num ; i++ ){
        printf("%d %d\n", X(x[i]), Y(y[i]) );
      }
    }
    #endif
    
    標準規格で同等の機能を実装することはできません。


  • 多次元配列変数を関数に渡す。

    gcc拡張文法でのC言語では多次元配列を次のようにして 関数に渡すことができます。C++言語では使えません。

    void func( int len, int a[len][len] )  /* GCC Extension of C */
    {
      int i, j;
      for( i=0; i<len; i++ ){
        for( j=0; j<len; j++ ){
          a[i][j] = i+j;
        }
      }
    }
    
    int a[4][4];
    func( 4, a );
    
    標準規格で実装するには、特殊なclassを用意することです。


  • 配列変数の初期化で要素を指定する。

    gcc拡張文法でのC言語では配列や構造体や共用体の変数の初期化で 次のようにして、代入先の要素を指定することができます。 C++言語では使えません。

    /* GCC Extensions of C */
    int a[6] = { [4] 29, [2] = 15 };
    int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
    
    struct point { int x, y; };
    struct point p1 = { y: 1, x: 2 };
    struct point p2 = { .y = 1, .x = 2 };
      
    union foo { int i; double d; };
    union foo f = { d: 4 };
    
    int whitespace[256] = { [' '] = 1, ['\t'] = 1, ['\n'] = 1, ['\r'] = 1 };
    
    標準規格で実装することはできません。


  • 総合目次
    Copyright(C) by Naoki Watanabe. Oct 21st, 1995.
    渡辺尚貴 naoki@cms.phys.s.u-tokyo.ac.jp