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

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

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)の マニュアル参照、このガイドで使えとあるのは以下のやつです *2

実際のコード例は↓

#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の時代はじまるよー(社会の授業が近現代については自習みたいに、ここで連載終わりにしていいかな?)。


*1:グローバル変数1万1000個に過剰反応するプログラマさんオッスオッス。
*2:うーん本当はARGSUSEDも使わないと引っかかるんだけど、インタフェースプログラミングが必要なとこ以外は関係ないせいで漏れてるのかな…
*3:gccのCパーサを使うよう変更するっても、まだ当時はpccと同じyacc/lexで書かれてたとはいえ一から書き直しレベルの作業の上にライセンスが…
*4:NetBSDの常でそういう誰もやらない作業はcore、特にchristos氏あたりの作業になりまんな。


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