● 関数属性の宣言
GNU Cでは,関数のプロトタイプ宣言を定義する際に,属性を宣言することによって,コンパイラによる関数呼び出しの最適化をすることができるようになります.
キーワード__attribute__によって,宣言をする際に特別な属性を指定することができます.このキーワードの後に,二重の丸括弧(())に囲まれた属性指定が続きます.現在,9個の属性,
noreturn,const,format,
no_instrument_function,section,
constructor,destructor,unused,
weak
が関数に対して定義されています.sectionを含むその他の属性が,変数宣言(後述の変数属性の指定を参照)と型(同じく後述の型属性の指定を参照)に対してサポートされています.
個々のキーワードの前後に__を付けて属性を指定することもできるので,同じ名前をもつマクロが定義済みであっても,問題はありません.
○noreturn
プログラム中で何らかの理由があり,呼び出し元に復帰しない関数が存在します.たとえば,関数中で異常終了させるような場合です.
このような関数をnoreturnと宣言することによって,その関数が復帰しないということをコンパイラに知らせることができます.リスト36〜リスト39に例を示します.
なお,これ以降,本項の検証にあたっては,コンパイル時に最適化オプション-O3を付加しています.
アセンブラからわかるように,noreturn属性は,abend_procが復帰することがないものと想定するようにコンパイラに指示します.コンパイラは,この場合には復帰することを考えず,復帰の後処理を省略しています.これにより,少しコンパクトなコードが生成されます.
なお,noreturn属性を指定した関数が,void以外の型の戻り値をもつのは無意味です.
noreturn属性は,バージョン2.5以前のGNU Cでは実装されていません.ちなみに,ある関数が復帰しないということを宣言するには,リスト40のような方法もあります.リスト41からわかるように,noreturn属性を使用したときとまったく同じように復帰の後処理を省略しています.
〔リスト40〕古いGNU Cでも可能な定義例(test70.c)
|
#include <stdio.h>
typedef void voidfn ();
volatile voidfn abend_proc;
int main(void)
{
abend_proc();
return;
}
void abend_proc()
{
printf("____abend_____ kita----");
exit(1);
}
|
|
〔リスト41〕test70.cから生成されたアセンブラ(test70.s)
|
.file "test70.c"
.version "01.01"
gcc2_compiled.:
.section .rodata
.LC0:
.string "____abend_____ kita----"
.text
.align 4
.globl abend_proc
.type abend_proc,@function
abend_proc:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
addl $-12,%esp
pushl $.LC0
call printf
addl $-12,%esp
pushl $1
call exit
.Lfe1:
.size abend_proc,.Lfe1-abend_proc
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
call abend_proc
.Lfe2:
.size main,.Lfe2-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
○const
多くの関数は,引き数以外の値を参照しないでしょう.そして戻り値を返すこと以外に全体に影響を及ぼすこともないでしょう.
このような関数には,算術演算子と同様に,連載第2回(2002年9月号)で説明した共通部分式削除やループの最適化を適用することができます.このような関数は,const属性を指定して宣言するべきです.リスト42〜リスト45に示す例ではkeisanという関数を何回か呼んでいますが,その結果は何度呼んでも変わらないので,アセンブラを見ると一度呼び出すだけで後は省略しています.
なお,const属性を指定した関数の戻り値がvoidになるのは無意味なことです.
○format (archetype, string-index, first-to-check)
format属性は,その関数が,書式文字列に照らし合わせて型チェックを行うべき引き数,すなわちprintf,scanf,strftimeのような方式の引き数を取ることを指定します.
archetypeパラメータは,書式文字列がどのように解釈されるかを決定するものです.printf,scanf,strftimeのいずれかでなければなりません.
string-indexパラメータは,どの引き数が書式文字列引き数であるかを数値で指定します.
また,first-to-checkは,書式文字列に照らし合わせてチェックすべき最初の引き数の番号です.チェックすべき引き数を指定できない関数に対しては,第3パラメータに0を指定してください.この場合,コンパイラは,書式文字列だけを対象にして整合性のチェックを行います.
○format_arg (string-index)
format_arg属性は,その関数がprintfやscanfのような方式の引き数を取り,それを修正した後にprintfやscanfのような関数に渡すということを指定します.
○no_instrument_function
オプション-finstrument-functionsを指定してコンパイルすると,ほとんどの関数の入口と出口において関数呼び出しのプロファイル処理を行うためのコードが生成されることになります.no_instrument_function属性をもつ関数については,そのようなコードの生成は行われません.
$ gcc -O3 -S -finstrument-functions test73.c
$ gcc -O3 -S -finstrument-functions test74.c
上のようにリスト46,リスト48をコンパイルした場合,それぞれリスト47,リスト49のようなコードが生成されます.const属性を付加した場合,関数呼び出しのプロファイル処理を行うためのコードが省略されています.
連載の第5回(2003年1月号)で説明していますが,-finstrument-functionsを指定すると,プロファイル用の関数が生成されます.
.globl __cyg_profile_func_enter
.globl __cyg_profile_func_exit
この関数を呼び出すためのコードがリスト49では省略されています.
○section (“section-name”)
通常,コンパイラは,生成したバイナリコードをtextセクションに置きます.しかし,ときには別のセクションを追加したり,特定の関数を特別なセクションに置く必要にせまられることがあります.section属性は,関数が特定のセクション内に置かれるよう指定します.
ただし,ファイル形式によっては,セクションの任意指定がサポートされていないものもあります.したがってsection属性は,すべてのプラットホームで利用できるわけではありません.あるモジュールのすべての内容を特定のセクションにマップする必要がある場合には,この属性ではなく,リンカの機能を使うことを検討してください.リスト50,リスト51に例を示します.
○constructor
constructor属性を指定された関数は,main()関数が実行される前に,自動的に呼び出されるようになります.main()関数が実行される前に暗黙のうちに使われるデータを初期化するのに役に立ちます.リスト52,リスト53に例を示します.
コンパイルして実行した結果は次のとおりです.
$ gcc -O3 -o test76 test76.c
$ ./test76
start
temp=10
リスト53に示す生成されたアセンブラには,
.section .ctors,"aw"
というセクションができています.これはC++言語のコンストラクタそのものです.
○destructor
destructor属性を指定された関数は,main()関数の実行が終了した後に,自動的に呼び出されるようになります.この機能は実行後の後処理に役に立ちます.
リスト54に示すソースをコンパイルして実行した結果は,次のとおりです.
$ gcc -O3 -o test77 test77.c
$ ./test77
program_start
main execute
program_end
リスト55に示す生成されたアセンブラには,
.section .dtors,"aw"
というセクションができています.これはC++言語のデストラクタそのものです.
○unused
この属性が関数に対して指定された場合,その関数は使われないはずであるという意味になります.GNU Cは,このような関数に対しては警告メッセージを出力しません.なお,C++ではパラメータをもたない定義は正当なので,現在のところGNU C++は,この属性をサポートしていません.
○weak
weak属性を指定された宣言は,globalシンボルではなく,weakシンボルとして出力されるようになります.これは主として,ユーザーのソースコード中で無効にすることのできるライブラリ関数を定義するのに役に立ちますが,関数以外の宣言においても使うこともできます.このシンボルは,ELFターゲットにおいてサポートされていますが,GNUのアセンブラとリンカを使っている場合には,a.outターゲットにおいてもサポートされています.
リスト56とリスト57を照らし合わせればわかるようにinit_proc1はweakシンボルとして,init_procはグローバルシンボルとして生成されています.
生成された実行形式を調べると,次に示すようにinit_procの属性はTで,init_proc1の属性はWとなっています.
$ nm test78 | grep init
08048298 ? _init
080483e8 t init_dummy
08048458 t init_dummy
08048424 T init_proc
0804841c W init_proc1
ここで,Wは「弱い定義(weak)」であることを示します.すなわち,このシンボルは定義されてはいますが,他のライブラリの定義によって上書きされることを示しています.通常の定義はTで示されます(init_proc).
新たにソースtest79.c(リスト58)を作り,その中でinit_proc1を再定義した場合,通常はリンクエラーとなりますが,weak属性が付いているので,新しいinit_proc1がリンクされます.
〔リスト58〕新しいinit_proc1のソース(test79.c)
|
#include <stdio.h>
void init_proc1();
void init_proc1()
{
printf("init_proc1");
}
|
|
$ gcc -o test78 test78.c test79.c
$ nm test78 | grep init
08048298 ? _init
080483e8 t init_dummy
08048478 t init_dummy
08048424 T init_proc
08048430 T init_proc1
これはデバッグ時に役立ちそうな機能です.
○alias (“target”)
alias属性を指定された宣言は,別のシンボルへの別名として出力されます.その別のシンボルは指定されていなければなりません.
リスト59に示すソースをコンパイル/実行した例を次にあげます.
〔リスト59〕alias属性を付加したソース(test81.c)
|
#include <stdio.h>
void init_proc1() __attribute__ ((weak, alias ("init_proc")));
void init_proc();
int main(void)
{
printf("start\n");
init_proc();
init_proc1();
return;
}
void init_proc()
{
printf("ver1.0\n");
}
|
|
$ gcc -o test81 test81.c
$ ./test81
start
ver1.0
ver1.0
その後,新しいinit_proc1のソース(リスト60)をリンクします.実行結果は,次のようになります.
〔リスト60〕新しいinit_proc1のソース(test80.c)
|
#include <stdio.h>
void init_proc1();
void init_proc1()
{
printf("ver1.1\n");
}
|
|
$ gcc -o test81 test81.c test80.c
$ ./test81
start
ver1.0
ver1.1
このように,関数init_proc1はtest80.cのものに置き換わりました.
これもやはりデバッグ時に役立ちそうな機能です.
○no_check_memory_usage
コンパイル時のオプション-fcheck-memory-usageが指定されていると,メモリアクセスの前に,サポートルーチンの呼び出しを行うコードが生成されますが,関数にこの属性を指定すると,その関数については,メモリチェックを行うコードが無効化されます.
この-fcheck-memory-usageオプションを指定すると,メモリチェックが有効となっている関数の中では,asmキーワードや,__asm__キーワードは使えませんが,no_check_memory_usageを指定した関数内では使用できます.
連載第5回で,-fcheck-memory-usageオプションについて説明しています.
○regparm (number)
Intel 386上では,regparm属性により,コンパイラは最高でnumberによって指定される個数までの整数引き数を,スタックではなくEAX,EDX,ECXレジスタに入れて渡すようになります.
○stdcall
Intel 386上では,stdcall属性により,関数が可変個数の引き数を取るのでない限り,引き数を渡すのに使われたスタック領域は呼び出された関数がPOPするものと,コンパイラは想定するようになります.
○cdecl
Intel 386上では,cdecl属性により,引き数を渡すのに使われたスタック領域は呼び出した関数がPOPするものと,コンパイラは想定するようになります.ここに挙げた3点は現在では古いアーキテクチャ固有のものであり,もう使うべきではないと思います.
○longcall
RS/6000とPowerPC上では,longcall属性により,コンパイラは常にポインタを使ってその関数を呼び出すようになります.これにより,現在の位置から64Mバイトを超えて離れた位置にある関数でも呼び出すことができます.
dllimport属性,dllexport属性,exception属性はPowerPC上で動作するWindows NT固有のものなので省略します.
○function_vector
このオプションは,H8/300とH8/300Hにおいて,指定された関数が関数ベクタを利用して呼び出されるべきであることを示すのに使います.
○interrupt_handler
このオプションは,H8/300とH8/300Hにおいて,指定された関数が割り込みハンドラであることを示すのに使います.
○eightbit_data
このオプションは,H8/300とH8/300Hにおいて,指定された変数が8ビットデータセクションに置かれるべきであることを示すのに使います.
○tiny_data
このオプションは,H8/300Hにおいて,指定された変数がtinyデータセクションに置かれるべきであることを示すのに使います.
○interrupt
このオプションは,M32R/Dにおいて,指定された関数が割り込みハンドラであることを示すのに使います.
○model (model-name)
この属性は,M32R/Dにおいて,オブジェクトのアドレス範囲を設定し,関数に対して生成されるコードを決定するのに使います.
次回は続けてGNU Cの拡張機能について,詳細に説明と検証を行います.
|