2013/11/19(Tue)
○[Unix C][*BSD][な特雑コ] sys/cdefs.hとは何ですか? (その6)
昨日の記事に引き続き4.4BSD-Liteで入った変更です。
@__pure マクロ
__pureマクロはその関数に副作用が無い事を保証する属性です、ガタッと音をたてて椅子から立ち上がった関数型言語ラーの皆様、こちらではありませんのでお座り下さい。
@副作用とは?
あんま説明するまでもないですが、副作用ありの例
int value = 0;
int
foo()
{
return value++;
}
int
main(void)
{
printf("%d\n", foo());
}
関数fooを呼び出した結果グローバル変数valueが書き換わりました、実行前と実行後でこいつの値が変わった事=副作用です。
勘違いしやすいですが、グローバル変数を参照するだけなら副作用はありません *1。
int value = 0;
int
bar()
{
return value + 1;
}
int
main(void)
{
printf("%d\n", bar());
}
関数bar()には副作用はありません(まだよわい)。
最もわかりやすい例としては、引数だけ参照するケースとかすな。
int
buzz(int value)
{
return value + 1;
}
int
main(void)
{
int value = 0;
printf("%d\n", buzz(value));
}
関数buzzには副作用はありません(つよい)。
何も参照しない完全勝利した淫夢君UCのガッツポーズはスルーして、と。
@「副作用なし」をコンパイラが知る事のメリット
副作用なしだとコンパイラに教えてあげると何かいいことしてくれるの?というと
- コンパイラで最適化してくれるかも(ループ最適化など)
- 並列プログラミング…あっそこの関数型言語に一言あるお客様、お静かにお願いいたします
みたいな感じ、後者はこの時点ではまだコンパイラでどーこーはアレだったと思われる。
@__pure マクロの実装
現在のgccでは昨日のnoreturnと同様に、__attribute__の機能を使ってconstあるいはpure属性を指定します。
extern int bar() __attribute__((pure));
extern int buzz(int) __attribute__((const));
const属性は厳格で引数のみを参照の場合、pureはもうちょい緩くて引数とグローバル変数を参照の場合です。
古いgcc(2.5以前)ではnoreturn属性は存在しなかったので、以下のようにconstとして宣言する必要がありました。
extern int const bar(int);
これも昨日のvolatile同様、今のconst型修飾子(変更不可)とは意味合いが異なるので今時のgccではエラーに。
Many functions do not examine any values except their arguments, and have no effects except the return value. Such a function can be subject to common subexpression elimination and loop optimization just as an arithmetic operator would be. These functions should be declared `const'. For example, extern int const square (); says that the hypothetical function `square' is safe to call fewer times than the program says.
すでにこんな機能constキーワードでなく#pragma使えやとdissられてますな。
Some people object to this feature, suggesting that ANSI C's `#pragma' should be used instead.
それに対して#pragmaじゃアカンという反論がありますが割愛、またC11の話する時に__attribute__とか__declspecの話交えて書くかも。
__pureマクロの実際の 使用例はこちら。
__pure int abs(int);
...
__pure long
labs(long);
現在の実装はこんな感じ、const属性は2.5から、pure属性は2.96なのでこのifdefです。
#if __GNUC_PREREQ__(2, 96)
#define __pure __attribute__((__pure__))
#elif defined(__GNUC__)
#define __pure __const
#else
#define __pure
#endif
@lint(1)の闇
ここでひとつ
オスシコンパイラ業界のえらい人の誤解を解いておくと「この時点(pcc時代)」だけでなく「現在(gcc/clang時代)も」lint(1)は*BSDのビルドシステムの中で使われています。
例えばNetBSDのmake(1)に付属する ドキュメントにも書いてありますが
LINT C program verifier. [lint] LINTFLAGS Options to ${LINT}. [-chapbxzFS]
という変数が定義され、またMakefileターゲットとして
lint: run lint on the source files
が用意されて(かつallターゲットに含まれる)、ビルド時にはソースコードチェックが必ず実行されます。
ですのでNetBSDの コーディングスタイルガイドには
- lint(1)の警告にひっかかるようなコードがある
- → だがしかしそれは意図したコーディングである場合
- → コメントにlintコマンド入れて消せ
という決まりになってます。
コマンドの一覧はlint(1)の マニュアル参照、このガイドで使えとあるのは以下のやつです *2。
- CONSTCOND … ifなんかの条件式が定数なのは(ifdefプリプロセッサ命令を知らないからでなく)意図的である
- NOTREACHED … 関数の戻り値を返さないのは(書き忘れたからではなく)意図的である
- FALLTHROUGH … switch文でbreakしてないのは(Pascalのcase文と混同してるからではなく)意図的である
実際のコード例は↓
#define MACRO(v, w, x, y) \
do { \
v = (x) + (y); \
w = (y) + 2; \
} while (/* CONSTCOND */ 0)
...
while ((ch = getopt(argc, argv, "abn:")) != -1) {
switch (ch) { /* Indent the switch. */
case 'a': /* Don't indent the case. */
aflag = 1;
/* FALLTHROUGH */
...
default:
usage();
/* NOTREACHED */
}
@元々lint(1)の開発はpccと一体だった
lint(1)はCのソースコードを処理するパーサ部はpccと同一コードベースです、 READMEにもその旨書いてありますな。
Most of lint's source files are shared with the portable compiler: they are found in /usr/src/lib/mip. The files here are only those which are unique to lint.
なもんでpccの時代であれば、仕様に改定があった場合pcc側のCパーサ( cgram.yとか scan.c)に手をいれるだけで OKだったんですが、pccを捨ててgccを導入した事でそうもいかなくなってしまったのよね *3。
なのでgccの__attribute__((noreturn)) と lint(1)の/*NOTREACHED*/ 両方書かないとアカンようになってしまい、同じことを2度やるのが嫌いな怠惰プログラマーには許せん状況になっています。
そもそもANSI/ISO-Cで関数プロトタイプが導入されたことで、lint(1)使わなくてもコンパイラ側で引数の間違いがチェック可能になった時点で それ以外のlint(1)でやってるエラーチェックもすべてコンパイラ側に寄せて、lint(1)は引退させるべきだった気がするんだけど、後の祭りですな。
ちなみに現在のlint(1)ですがC99にすらまだ完全対応してません、マニュアルより。
-S C9X mode. Currently not fully implemented.
ragge@による 新生pccにはlint(1)は含まれないし、更にこれからC11対応もあるのに誰がやるのよレベル *4。おそらく今後数年でlintを無視するための事前定義マクロ__lint__でお茶を濁しまくって可読性下がるんじゃないかな。
以下はlint(1)がC99対応してないせいで #ifdef __lint__ してる 例、つか今はもうこれ大丈夫な気もするけど。
#if defined(_REENTRANT) && !defined(__lint__) /* XXX lint is busted */
#define STDEXT { ._lock = MUTEX_INITIALIZER, ._lockcond = COND_INITIALIZER }
struct __sfileext __sFext[3] = { STDEXT,
STDEXT,
STDEXT};
#else
struct __sfileext __sFext[3];
#endif
そしてNetBSDは Coverityの支援を受けていて、ビルドシステムとは別途に静的ソース解析も実施しているので、lint(1)要らなくね?という気がしてなりません。
まぁそうするとエラーチェックがgccベタベタになって困るってのもあるけど、どーせみんなgcc互換にするよね?(威圧)
@次回予告
いよいよNetBSD/FreeBSDの時代はじまるよー(社会の授業が近現代については自習みたいに、ここで連載終わりにしていいかな?)。
*2:うーん本当はARGSUSEDも使わないと引っかかるんだけど、インタフェースプログラミングが必要なとこ以外は関係ないせいで漏れてるのかな…
*3:gccのCパーサを使うよう変更するっても、まだ当時はpccと同じyacc/lexで書かれてたとはいえ一から書き直しレベルの作業の上にライセンスが…
*4:NetBSDの常でそういう誰もやらない作業はcore、特にchristos氏あたりの作業になりまんな。