概要
業務で関わっているC言語のレガシーコードについて、色々調べて興味深かったことを2点まとめました。
記事内の機能が使われていた当時を直接体験したわけではないので、推測を多く含みます。
※当然、業務での具体的なソースコードはこの記事には出てきません。
1. 「__P」というマクロ
最初に結論
- K&R形式のコンパイラとANSI C準拠のコンパイラ間で関数宣言の互換性を保つために、マクロが使用されていた
詳細
業務で扱うC言語のヘッダファイルで、以下のような関数宣言を見かけました。
※関数名や引数名は適当です。
int samp_func __P((int p1, int p2));
他の箇所の使われ方から「samp_func」という名前の関数宣言であろうということは想像できるのですが、この「__P」はなんぞや、ということで調べてみました。
__Pマクロの内容はおおむね以下のようになっています。
※業務で使用している環境のものは直接書けないので、オープンソースで参照できるものの中からOpenBSDで使用されているものを引用させていただいています。
#if defined(__STDC__) || defined(__cplusplus)
#define __P(protos) protos /* full-blown ANSI C */
/* (中略) */
#else /* !(__STDC__ || __cplusplus) */
#define __P(protos) () /* traditional C preprocessor */
今のコンパイラ(ANSI C準拠)では、上記の例は以下のように展開されます。
int samp_func (int p1, int p2);
そうでないコンパイラ、具体的にはANSI C以前の、K&RスタイルのC言語にしか対応していないコンパイラでは、以下のように展開されます。
int samp_func ();
K&Rの関数宣言では、関数宣言に引数を記述する必要が無かったようです。
このマクロがあるということは、その時代のK&Rのみ対応コンパイラは、関数宣言に引数があるとコンパイルエラーになるような仕様であったと思われます。
K&Rのみ対応のコンパイラと、それ以降のANCI C準拠のコンパイラの両方に対応するソースコードを記述するために、このようなマクロが出来たようです。
余談
自宅で確認できる環境(Ubuntu 24.04.1 LTS)だと、互換性のため以下のように定義されていました。
/* These two macros are not used in glibc anymore. They are kept here
only because some other projects expect the macros to be defined. */
#define __P(args) args
#define __PMT(args) args
2. 「ansi2knr」というコマンド
最初に結論
- K&R形式のコンパイラとANSI C準拠のコンパイラの両方でコンパイルできるように、ansi2knrというコマンドが使用されていた
- そのコマンドの仕様が影響して、戻り値の型の後に改行を含む関数定義が多く書かれるようになったのではないかと想像している
詳細
こちらは、項目1を調べている際、意図せず歴史に触れた項目です。
同じく業務で扱うC言語のソースファイルの関数定義で、以下のように戻り値の型と関数名の間に改行が入っているソースコードが目立ちました。
※関数名や引数名は適当です。
int
samp_func(int p1, int p2)
{
/* (中略) */
}
別にANSI Cに違反しているわけでもないので、昔書いてた人がたまたまそういうコーディングスタイルだったのかな、位にとらえていました。
そんな中、項目1を調べているときに、K&Rスタイルの関数定義が以下のようになっていることを知りました。
#include <stdio.h>
int main(argc, argv)
int argc;
char* argv[];
{
printf("K&R test\n");
return 0;
}
このスタイルは、C23で削除されるまで、C言語の規格としては有効だったようです。
手元の環境では以下のような扱いで、オプションを付けても警告止まりでした。
- Visual Studio 2022(Version 19.42.34436)では、警告レベル4の時にC4131警告
- gcc(Ubuntu 13.3.0-6ubuntu2~24.04)では、-Wold-style-definition オプションをつけた時に警告
- clang(Ubuntu clang version 18.1.3)では、何もオプションをつけなくても警告
このスタイルに関して、ansi2knrというコマンドが存在していたことを知りました。
インターネットで参照できるFreeBSDのマニュアルへのリンクを張っておきます。
このコマンドは、ANSI Cスタイルで書かれたC言語ソースコードを、K&Rスタイルのソースコードに変換するためのものです。
- K&Rのみ対応のコンパイラを使用するときは、ansi2knrコマンドを通す
- ANCI C準拠のコンパイラを使用するときは、特に何もしない
このようにMakefileなどで制御することで、どちらの環境でもコンパイルできるようにする運用が行われていたようです。
そして、このansi2knrコマンドの仕様の一部を、上記FreeBSDのリンクから引用します。
ansi2knr recognizes function definitions by seeing a non-keyword identifier at the left margin, followed by a left parenthesis, with a right parenthesis as the last character on the line, and with a left brace as the first token on the following line (ignoring possible intervening comments).
拙訳
ansi2knr は、左端にキーワードでない識別子があり、その後に左括弧が続き、右括弧が行の最後の文字であり、次の行の最初のトークンが左中括弧であることで関数定義を認識します(途中のコメントは無視されます)
結構制限のある仕様です。そして、どこかで見たことのあるスタイルです。
そう、この項目の最初で例に上げた、戻り値の型と関数名の間に改行がある形の関数定義です。
int
samp_func(int p1, int p2)
{
/* (中略) */
}
ここから、当時のコーディングスタイルに対する想像を膨らませています。
- K&RスタイルからANSI Cに移行する過渡期で、上記ansi2knrコマンドが使われた
- このansi2knrの仕様に合わせるため、戻り値の型と関数名の間に改行がある形の関数定義を余儀なくされた
- その関数定義のスタイルで書くことに慣れ、ansi2knrを使う必要がなくなった後も、ある程度の期間は同じスタイルで関数定義するようになった
- その結果、レガシーなコードに、戻り値の型の後に改行を含む関数定義が残るようになった
想像終了
余談
ansi2knrのソースコードは、検索するといくつかのサイトで見つかりました。
Debianのサイトへのリンクを貼っておきます。
感想
- プログラミング言語に歴史あり
- コンパイラに歴史あり
- ソースコードに歴史あり
- そのことに想いを馳せると、業務で扱うレガシーなソースコードに対して少しだけ優しくなれる……かも