GCCで提供されている関数の"__attribute__" 機能について極々簡単に紹介する。基本的に unixwiz.net の "Using GNU C __attribute__" のざっくりとした要約になっている。なお、ここで紹介している機能を有効にするには-Wallオプションを指定する必要がある。
※動作確認はCentOS5.2上のgcc-4.1.2, binutils-2.17.50.0.6 上で行っている。
printfやscanfのような、可変長引数群を書式指定文字列に埋め込んだりする関数で、書式指定文字列と可変長引数群の組み合わせを「コンパイル時に」チェックできるようになる。
format (archetype, string-index, first-to-check)
"archetype"は "printf", "scanf" のようにチェック時に参考にする関数名を指定する。gcc側で選択肢は決められており、GCC4の場合は次の4つのいずれかとなる。
printf, scanf, strftime, strfmon
"string-index"は書式指定文字列が引数の何番目かを指定する(1始まり)。
"first-to-check"は書式に埋め込む・あるいは関連する可変長引数が何番目から始まるかを指定する(1始まり)。
printfの例を簡単に紹介する。
format_test.c:
// p1,p2,p3は適当に挟み込んだ引数。 // arhetype = printf形式 // string-index : 3番目の引数、const char *formatを書式指定文字列としてチェック // first-to-check : p3をスキップし、5番目の引数からformatとの組み合わせをチェック extern void myprintf(int p1, int p2, const char *format, int p3, ...) __attribute__ ((format(printf, 3, 5))); void foo() { myprintf(1, 2, "s = %s\n", -1, 5); // "%s"なのにintの5 myprintf(1, 2, "n = %d, %d, %d\n", -1, 1, 2); // 引数が足りない }
まず"-Wall"無しでコンパイルすると、エラーも警告も表示されず、コンパイルは正常に終了する。
$ gcc -c format_test.c
続いて "-Wall" をつけるとformatとのミスマッチについて警告が表示されるようになる。
$ gcc -Wall -c format_test.c format_test.c: In function ‘foo’: format_test.c:6: 警告: format ‘%s’ expects type ‘char *’, but argument 5 has type ‘int’ format_test.c:7: 警告: フォーマットへの引数が少なすぎます
abort(3)やexit(3)は、呼んだ関数から戻らない。GCCはこれらについては自動的に判別する。それ以外の、ユーザーが定義した関数で呼び出し元へ戻らない場合は noreturn を使うことで警告表示を消すことが出来る。
文章だけでは分かりづらいので、実際の例を示す。まず警告が表示されるパターンを示す。
noreturn_test.c:
extern void exitnow(); int foo(int n) { if (0 < n) { exitnow(); // returnせずにfoo()が終了する } else { return 0; } }
"-Wall"付でコンパイルすると、exitnow()のルートでreturn無しでfoo()が終了する為警告が表示される。
$ gcc -Wall -c noreturn_test.c noreturn_test.c: In function ‘foo’: noreturn_test.c:10: 警告: control reaches end of non-void function
__attribute__( (noreturn) ) を付けて実行すると、警告は表示されなくなる。
extern void exitnow() __attribute__((noreturn)); (以下同じ)
コンパイルしてみる。
$ gcc -Wall -c noreturn_test.c $
exit(3)やabort(3)の宣言を確認してみると、同様にnoreturnが指定されていることが確認出来た(ただし、"__noreturn__"とアンダースコア付になっている)。
/usr/include/stdlib.h:
extern void abort (void) __THROW __attribute__ ((__noreturn__)); extern void exit (int __status) __THROW __attribute__ ((__noreturn__));
副作用が無く引数のみで処理が完結し、引数のポインタの内容を見ない関数の場合に、gcc側でキャッシュなど最適化処理を行うようになる。stdlib.hではabs(3)やdiv(3)などで使われている。
より制限が緩いものに pure 属性もあり、こちらはstrlen(3)などポインタの内容を見る場合にも適用出来る。
/usr/include/stdlib.h:
extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur; extern div_t div (int __numer, int __denom) __THROW __attribute__ ((__const__)) __wur;
/usr/include/string.h:
extern size_t strlen (__const char *__s) __THROW __attribute_pure__ __nonnull ((1));
複数の"__attribute__"を組み合わせることも出来る。"__attribute__"を連続して続けることも出来るし、一つの"__attribute__"の中にまとめることも出来る。
例:
extern void die1(const char *format, ...) __attribute__((noreturn)) __attribute__((format(printf, 1, 2))); extern void die2(const char *format, ...) __attribute__((noreturn, format(printf, 1, 2))); int foo(int n) { switch (n) { case 1: die1("n = %d\n", n); case 2: die2("n = %d\n", n); default: return n; } }
__attribute__機能はGCC以外のコンパイラでは無視されても問題ないよう、注意深く設計されている。空のマクロに展開することで、GCC以外のコンパイラに対応出来る。__attribute__は複数のパラメータを取るようになっているが、これまでの例のように2重括弧でくくることで一つの引数としてマクロで扱えるようになる。
#ifndef __GNUC__ # define __attribute__(x) /*NOTHING*/ #endif
最後に、その他の注意点として関数属性は宣言時にのみ指定できる点に注意する。
関数の定義部では使えない。
このため、関数属性を使う時は必ずプロトタイプ宣言を用意してそちらに"__attribute__"を指定する必要がある。
コメント