The Man Who Fell From The Wrong Side Of The Sky:2013年11月18日分

[最新版] [一覧] [前月] [今月] [翌月]

2013/11/18(Mon)

[Unix C][*BSD][絶許、訴訟] sys/cdefs.hとは何ですか? (その5)

4.3BSD Net/2で「K&RとANSI/ISO-Cのポータビリティレイヤ」として初めて導入されたsys/cdefs.hですが、前回触れた ANSI/ISO-Cのキーワード周り

というifdefが入ってしまったことで、その後の*BSDにおいて「Cコンパイラの実装の差異を吸収する」役割に変化します。

@ 4.3BSD Net/2 → 4.4BSD(聖域化)

Cコンパイラをpccからgccに置き換えたNet/2をベースに、フリーでは 386BSDそして商用では BSD/386が生まれるわけですが

ここちょっと消えてますね、44番という選手がいたんですけど 。

行方不明だったBSD選手(背番号44)は ライトにコンバートされて発見されますが この時点では sys/cdefs.hには更にgcc独自機能のラッパー(Dr. Dre感)マクロが追加されています。

/*
 * GCC has extensions for declaring functions as `pure' (always returns
 * the same value given the same inputs, i.e., has no external state and
 * no side effects) and `dead' (nonreturning).  These mainly affect
 * optimization and warnings.  Unfortunately, GCC complains if these are
 * used under strict ANSI mode (`gcc -ansi -pedantic'), hence we need to
 * define them only if compiling without this.
 */
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
#define __dead __volatile
#define __pure __const
#else
#define __dead
#define __pure
#endif

@ __dead マクロ

以下の例のように、関数内でexit(3)やabort(3)などを呼ぶ事で決して元の関数に戻らないケースは多々あります。

#include <stdlib.h>

void
suicide(void)
{
	exit(EXIT_FAILURE);
}

int
main(void)
{
	suicide();
}

このコードをコンパイルしようとするとgccは警告を出します *1

$ gcc -Wall test.c
test.c: In function 'main':
test.c:13:1: warning: warning: control reaches end of non-void function

プログラミング言語C 戻り値を返さず死亡、というシナキーンいかんでしょ(9800点)なミスはありがちです。

Cコンパイラにpccを使用していた頃はこのようなミスを発見する 静的コード解析ツールはlint(1) *2コマンドの出番でした(もちろん今でも残っていますが)。

$ lint test.c
test.c:
...
test.c(13): warning: function main falls off bottom without returning value [217]
...
Lint pass2:

しかしプログラムにおいてmain関数には戻らずイベントハンドラで終了というのはよくあることです *3。ですのでこの警告が煩わしい場合が多々あります。

lint(1)の場合はコメントにNOTREACHEDコマンドを埋め込むことでこの警告を抑制可能です。

int
main(void)
{
	suicide();
	/*NOTREACHED*/
}

ところがgccは当然ですがこのlint(1)のコマンドを認識しません、gccでこの警告を抑制するには

extern void suicide(void) __attribute__((noreturn));

のように返らない関数に対してノータリンじゃなくてnoreturn属性を指定する必要があります。 この属性(__attribute__)というのはgcc独自機能です。

古いgcc(2.5以前)ではnoreturn属性は存在しなかったので、以下のようにvolatileとして宣言する必要がありました。

extern void volatile suicide(void);

今のvolatile型修飾子(最適化抑止)とは意味合いが違うことに注意、ですので今時のコンパイラではエラーになります。 gcc.infoには

    The `volatile' keyword tells the compiler to assume that `fatal'
cannot return.  This makes slightly better code, but more importantly
it helps avoid spurious warnings of uninitialized variables.

    It does not make sense for a `volatile' function to have a return
type other than `void'.

と書いてありますやな。

実際の使用例としてはinfoに「fatal」とある通り、 err.hあたりの致命的エラーでメッセージ吐いて死ぬ関数とかですな。

__dead void	err __P((int, const char *, ...));
__dead void	verr __P((int, const char *, _BSD_VA_LIST_));
__dead void	errx __P((int, const char *, ...));
__dead void	verrx __P((int, const char *, _BSD_VA_LIST_));

現在の実装は、gcc2.5以下ならvolatileキーワード、それ以上ならnoreturn属性使っとりますやね。

#if __GNUC_PREREQ__(2, 5)
#define __dead          __attribute__((__noreturn__))
#elif defined(__GNUC__)
#define __dead          __volatile
#else
#define __dead
#endif

@ 次回予告

今回書ききれんかった__pureマクロについて、そして gcc(1) と lint(1) の危うい関係についてお話します。


*1:ちなgcc4.5.3なのでエラーメッセージは当時と違うと思われ、環境作るのめんどいわー。
*2:CentOSとかCygwinをお使いであればsplintをどうぞ
*3:GUIプログラミングなんかだと例えばXtAppMainLoop(3)とかですね。


[ホームへ] [ページトップへ]