読者です 読者をやめる 読者になる 読者になる

muranoyaのブログ

https://github.com/muranoya

C言語の構文いろいろ

C言語の文法は難しくはないと思うが,面倒な部分が多い.

ポインタ周りは特にそうだし,C99でCompound-literalが入ってから,どこまでがキャスト式なのかわかりづらくなった.

ポインタ周りに関しては,例えば

const int *ids;

のようなポインタの指し示す先がconstという宣言はよく見る.

ポインタ自体をconstにしたいときは,

const int *const ids;

とする必要がある.アドレス自体をconstにしているコードは,私はあまり見たことがない(そもそもちゃんとconstをつけている人もまた少ないように思う.).

ここで,関数ポインタを考えると,intの引数を1つ取って,doubleを返す関数は次のように書ける.

double (*f)(int);

これは次のように使う.

static double test(int x)
{
    return x * 3.14;
}

int main(int argc, char *argv)
{
    double (*f)(int) = test;

    printf("%lf\n", f(10));
    return 0;
}

関数宣言にstaticをつけると,内部リンケージになる.内部リンケージでは,その関数を含むファイル内でしか参照できない.何もつけずに宣言すると,externをつけているものとして解釈される(C99の仕様書6.2.2 Linkages of identifiersの5).

関数ポインタはだいぶ面倒な書き方になった.関数ポインタを返す関数の関数ポインタや,関数ポインタをとる関数の関数ポインタなどを考えるともっと複雑なことになる.

関数ポインタはtypedefして扱うのが読みやすくなっていいと思っているが,typedefについては少し注意が必要.typedefで検索すると,

typedef <別名をつける型> <新しい型名>

などと言っているところがあるが,これは間違っている.

typedefはexternやstatic,registerなどと同じstorage-class-identifierに分類されている.

つまり,typedef-declarationなどというものはなく,typedefはstaticやregisterと立ち位置は同じである.よってtypedefで別名を定義するには,まず,普通に変数を宣言するように書く.

double (*f)(int);

次に,staticやregisterをつける感覚で,

typedef double (*func)(int);

とする.これでdoubleを返してintを引数に取る関数の関数ポインタが定義できた.名前はfuncだ.

さっきのプログラムはこうなる.

typedef double (*func)(int);

static double test(int x)
{
    return x * 3.14;
}

int main(int argc, char *argv)
{

    // typedef double (*func)(int);はここでもよい
    func f = test;

    printf("%lf\n", f(10));
    return 0;
}

 

C99からCompound-literalが追加された.これはもともとGCCの拡張として存在した機能.

どういうものかは,ここらへんを見て頂ければわかる.

これはキャスト式と判別しにくい.理由は

double x = (double)(int){1};

このようなコードも当然許される.compound-literalを使ってintの無名変数をdoubleにキャストしている.これで構文解析が少し面倒になる….