Help us understand the problem. What is going on with this article?

Ancient C探訪記:関数編

More than 1 year has passed since last update.

おことわり: この記事では「1975年頃のC言語」仕様を解説します。2017年現在のC言語仕様とは異なるため、あなたのC言語ライフには役立たないことを予めご承知おきください。

(本投稿は Ancient C探訪記 シリーズの一部です。)

Ancient Cと標準C(ANSI C、C90)とでソースコードの見た目が大きく変わる、「関数(function)」まわりの構文規則や言語仕様についてみていきます。

Ancient Cの関数

関数宣言

"C Reference Manual" §8.4 Meaning of declarators より部分引用します。関数宣言では関数名と戻り値型のみを記述し、その関数がとる具体的な引数リストを 記述しません。つまり、関数宣言だけでは “何個の引数を指定するか” や “各引数の型は何か” という情報を一切読み取れません。

If a declarator has the form

D( )

then the contained identifier has the type "function returning ...", where "..." is the type which the identifier would have had if the declarator had been simply D.

関数呼び出し元では関数引数の型を知りえないため、実引数では自動型変換 floatdoublecharint が常に行われます(§7.1.6)。言い換えると、関数という境界を越えて floatchar の値をそのまま受け渡し出来ないのです。

Any actual arguments of type float are converted to double before the call; any of type char are converted to int.

関数定義

関数定義は "C Reference Manual" §10.1 External function definitions に記述があります。同セクションではEBNF(風)による形式的定義がなされますが、プログラミング言語仕様定義に馴染みが無いと解釈し辛いと思いますので1、具体的なソースコードで見ていきましょう。

§10.1に例示のある「与えられた3個の整数値のうち最大値を返す関数max」を定義してみます。引数リスト a, b, c には 引数名のみ を列挙したうえで、引数リストと関数本体の間)から{の間)でそれぞれの引数型を宣言します。今回テーマからは外れますが、Ancient Cの変数宣言には初期値を指定できず(int m = ...;はNG)、return文に括弧は必須(例えばreturn m;はNG)です。

ancient-func1.c
int max(a, b, c)
int a, b, c;
{
  int m;
  m = (a>b) ? a : b;
  return(m>c ? m : c);
}

さらに、Ancient Cでは「関数戻り値型」や「関数パラメータの型」を 省略可能 です。型省略時は暗黙に int とみなされるため、先の関数定義は次のようにも記述可能です:

ancient-func2.c
max(a, b, c)
{
  int m;
  m = (a>b) ? a : b;
  return(m>c ? m : c);
}

標準Cでの名残

標準C(ANSI C、C90)だと関数 max の宣言/定義は次のように記述します。見慣れた書き方ですね:

stdc-func1.c
/* 関数宣言 */
int max(int, int, int);

/* 関数定義 */
int max(int a, int b, int c)
{
  int m = (a>b) ? a : b;
  return m>c ? m : c;
}

実は ANSI C/C90 時点では、後方互換性のため “型省略時は暗黙に int” と “引数型を分離して宣言する” という古代の仕様が残っており、次のようにも書けるのです:

stdc-func2.c
/* 関数宣言 */
extern max();

/* 関数定義 */
max(a, b, c)
int a, b, c;
{
  /*(省略)*/
}

静的型システムを採用するモダン・プログラミング言語から見ると酷いゆるふわ仕様ですが、ご安心ください。C99以降では、Ancient C由来の「暗黙のint」「引数型の分離宣言」は 言語仕様から削除 されています。またC++言語にもこの古い仕様は引き継がれていません。

registerキーワード

コンパイラが進化した今日では利用価値がなくなったregisterキーワードですが、古の時代では有効な最適化手段の一つでした。"C Reference Manual" §8.1 Storage class specifiers より該当箇所を引用します。

There are some severe restrictions on register identifiers: there can be at most 3 register identifiers in any function, and the type of a register identifier can only be int, char, or pointer (not float, double, structure, function, or array). Also the addressof operator & cannot be applied to such identifiers. Except for these restrictions (in return for which one is rewarded with faster, smaller code), register identifiers behave as if they were automatic. In fact implementations of C are free to treat register as synonymous with auto.

Ancient Cには「関数内のregister変数は最大3つまで」という制限があったようです。へー。ちなみに標準Cだと個数制限への言及はありません。

適当に明日以降につづきます。


  1. C/C++言語の構文規則のうち、関数定義も含めた「宣言(Declarations)」構文定義は非常に難解な仕様定義となっています。コンパイラ構文解析器(parser)のお気持ちになって規則通り解釈していけば、広く知られているC/C++言語の宣言構文となることが読み取れるはずです。しかしこれを行うと次から言語法律家にマークされるという危険も伴う、諸刃の剣。 素人にはお薦め出来ない。 

yohhoy
「なんにも知らないって、すっごくしあわせ!」--スヌーピー
https://yohhoy.hatenadiary.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away