C言語関係掲示板

過去ログ

No.946 コンパイラは、どの部分までを読み込むと、関数であるとわかるか

[戻る] [ホームページ]
No.12042

関数について
投稿者---nimo(2004/01/22 18:01:09)


いつもお世話になっております。
今回お聞きしたいのは、関数についてです。

関数は、戻り値 関数名(引数){で構成されているのですが、
どの部分までを読み込むと、関数であるとわかるのでしょうか?
と申しますのも、
main()だけでも関数だといえますし、また、意地悪をして、
int main
(void) {
や、
int main
(void
){
と中途半端に改行を打っても、動くからです。
いったいどこを見て関数と判断するのかが気になります。
教えてください。('{'まで見ないとでしょうか)

No.12045

Re:関数について
投稿者---YuO(2004/01/22 20:44:21)


>関数は、戻り値 関数名(引数){で構成されているのですが、
>どの部分までを読み込むと、関数であるとわかるのでしょうか?

何を知りたいのですか?
規格には,どのように解釈するかが載っていますし,
プログラミング言語Cの第2版にもC89のDraftに基づく文法が載っています。

細かいことはコンパイラに関する書籍などを見てもらうとして……。


>main()だけでも関数だといえますし、また、意地悪をして、

現在の規格において,main()という形は関数呼び出し以外にありえません。
#C90ではfunction-definitionのdeclaration-specifiersはOptionalだったが,C99では必須。


>int main
>(void) {
>や、
>int main
>(void
> ){
>と中途半端に改行を打っても、動くからです。
>いったいどこを見て関数と判断するのかが気になります。
>教えてください。('{'まで見ないとでしょうか)

結論としては,構文要素としてfunction-definitionの形であった場合,最終的に関数として扱われます。
function-definition:
    declaration-specifiers declarator declarator-list(opt) compound-statement

#declaratorはFunction declaratorであること。
さらに,このfunction-definitionが出てくるのは,external-declarationの一部としてのみで,
external-declarationはtranslation-unitの一部としてのみ出てきます。
translation-unit:
    external-declaration
    translation-unit external-declaration

external-declaration:
    function-definition
    declaration


ちなみに,例に挙げられた二つはどちらも,翻訳フェーズ7で前処理字句が字句になったときに
Keyword
int
Identifier
main
Punctuator
(
Keyword
void
Punctuator
)
Punctuator
{
Punctuator
}

として処理されます。
#最後の}は後での構文解釈用に追加。

字句を単位として処理するので,ソース中の空白類文字は,
文字定数及び文字列リテラル中(それぞれのワイド版含む)を除いて,
全て字句をわけるためのみに使われます。
#前処理段階では改行は保持される。

そして,構文解釈を行ったときに,
function-definition
  declaration-specifiers
    type-specifier
      'int'
    declaration-specifiers(opt) : empty
  declarator
    pointer(opt) : empty
    direct-declarator
      direct-declarator
        identifier : main
      '('
      parameter-type-list
        parameter-list
          parameter-declaration
            declaration-specifiers
              type-specifier
                'void'
              declaration-specifiers(opt) : empty
            abstract-declarator(opt) : empty
      ')'
  declaration-list(opt) : empty
  compound-statement
    '{'
    block-item-list(opt) : empty
    '}'

のように解釈されます。


No.12052

Re:関数について
投稿者---かずま(2004/01/22 22:56:36)


改行をいくつ入れても意地悪にはなりません。単にスペースと同じです。

static const unsigned long int *f(void) { return NULL; }
static const unsigned long int *a[3];
static const unsigned long int *p;
static const unsigned long int v;
static const unsigned long int (*pf)(void);
static const unsigned long int (*apf[5])(void);
static const unsigned long int (*fpa(void))[5];

キーワードや記号などをどんどん読み込んで、新しい名前にぶつかったとき、
その右側を見て、その名前が何か判断できます。できなければ、左を見ます。

f は、右に ( が来るので、関数。
a は、右に [ が来るので、配列。
p は、右に ; が来て終了なので、左を見て * があるので、ポインタ。
v は、右に ; が来て終了なので、左を見て const unsigned long int が
あるので、その型の変数。
pf は、右に ) があるので、左を見て * があるので、ポインタ。
apf は、右に [ があるので、配列。
fpa は、右に ( があるので、関数。


要するに、名前の右に ( があれば、その名前は関数。
でなくて、名前の右に [ があれば、その名前は配列。
でなくて、名前の左に * があれば、その名前はポインタ。
でなければ、単純変数。

ただし、typedef名があると事情は変わりますが、これは新しい名前ではないので、
やはり上の原則で大丈夫でしょう。
また、構造体という問題もありますが、またそれは後で。


No.12066

Re:関数について
投稿者---nimo(2004/01/23 11:46:16)


わかりやすい解説、どうもありがとうございます。

>キーワードや記号などをどんどん読み込んで、新しい名前にぶつかったとき、
その右側を見て、その名前が何か判断できます。できなければ、左を見ます。

>要するに、名前の右に ( があれば、その名前は関数。
でなくて、名前の右に [ があれば、その名前は配列。
でなくて、名前の左に * があれば、その名前はポインタ。
でなければ、単純変数。

これで、関数の見分け方が少し見えてきました。

>ただし、構造体という問題もありますが、またそれは後で。

そうですね。構造体も関数の定義と同じように{}でくくってあるので、
その見分け方も必要になってきますね。
後、関数本体(定義部分)と、関数宣言部分との区別もありますが、これは自分でやります。