● 型属性の指定
キーワード__attribute__により,struct型とunion型を定義する際,その型に特殊な属性を指定することができます.このキーワードの後に,2重の丸括弧(())に囲まれた属性指定が続きます.現在,3個の属性aligned,packed,transparent_unionが型に対して定義されています.
属性はいずれも,キーワードの前後に__を付けて指定することもできます.これにより,同じ名前をもつマクロが定義済みであるかどうかを心配することなく,ヘッダファイルの中でこの属性を使うことができるようになります.たとえば,alignedの代わりに__aligned__を使うことができます.
aligned属性とtransparent_union属性は,typedef宣言の中において,あるいは完結した列挙型,構造体型,共用体型の定義の終端の波括弧 }の直後において指定することができます.また,packed属性は,定義の終端の波括弧 }の後においてのみ指定することができます.
また,終端の波括弧 }の後ではなく,列挙タグ,構造体タグ,共用体タグと型の名前の間において,属性を指定することもできます.
○aligned (alignment)
この属性は,指定された型の変数の最小のアラインメントの値を(バイト単位で)指定します(リスト26).
〔リスト26〕alignedを指定したソース(test100.c)
|
#include <stdio.h>
//型属性の指定 aligned (alignment)
struct data1 { short f[3]; } __attribute__ ((aligned (8)));
struct data2 { short f[3]; };
struct data1 dataa_;
struct data1 datab_;
struct data2 datac_;
struct data2 datad_;
int main(void)
{
asm("nop");
datab_ = dataa_;
asm("nop");
datad_ = datac_;
asm("nop");
return 0;
}
|
|
この場合,ダブルワード単位に転送することができるので,実行時の効率が向上します.
必要に応じてアラインメントの数値を指定できますが,リンカによっては制限がある場合があります.
リスト27のようにeaxレジスタとaxレジスタで,それぞれ32ビット転送と16ビット転送を行っています.
〔リスト27〕alignedを指定したソースから生成したアセンブラ(test100.s)
|
.file "test100.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
#APP
nop
#NO_APP
movl dataa_,%eax
movl %eax,datab_
movl dataa_+4,%eax
movl %eax,datab_+4
#APP
nop
#NO_APP
movl datac_,%eax
movl %eax,datad_
movw datac_+4,%ax
movw %ax,datad_+4
#APP
nop
#NO_APP
xorl %eax,%eax
jmp .L2
.p2align 4,,7
.L2:
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.comm dataa_,8,8
.comm datab_,8,8
.comm datac_,6,2
.comm datad_,6,2
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
○packed
前項「変数の属性の指定」でpackedを説明しましたが,型の指定でこれを行うと変数ではなく,その型全体がpacked属性になります.つまりstruct型とunion型に対してこの属性を指定するのは,構造体または共用体の個々のメンバにpacked属性を指定するのと同等です.
○transparent_union
名前のとおり,透過性共用体とでもいいましょうか.unionの型定義にこれが指定された場合,その共用体の型をもつ関数パラメータがあると,その関数の呼び出しが特殊な方法で扱われるようになります.
これには,大きく分けて二つの機能があります.
一つ目の機能:transparent_union属性が指定された共用体型に対応する引き数は,その共用体に定義された任意のメンバの型をもつことができます.キャストは必要ありません.また,その共用体のメンバにポインタ型のものがあれば,対応する引き数にはヌルポインタ定数またはvoidポインタ式を使うことができます.
なお,その共用体のメンバにvoidポインタ型のものがあれば,対応する引き数には任意のポインタ式を使うことができます.
二つ目の機能:関数に対してその引き数が渡される際には,transparent_union属性が指定された共用体自体の呼び出し規約ではなく,その共用体の1番目のメンバの呼び出し規約が使われます.
この機能は次のような場合に有用です.たとえば,互換性のために複数のインターフェースをもつライブラリ関数に使用できます.くわしく説明すると,wait関数はPOSIXとの互換のためにint *型の値を受け付けなければなりません.その一方で,4.1BSDインターフェースとの互換のためにunion wait *型の値を受け付けなければならないのです.
もしwaitのパラメータがvoid *型であったとすると,waitはどちらの引き数も受け付けるでしょうが,その他の任意のポインタ型も受け付けることになってしまい,引き数の型チェックがあまり役に立たなくなるでしょう.こうする代わりに<sys/wait.h>では,そのインターフェースを次のように定義することができます.
typedef union
{
int *__ip;
union wait *__up;
} wait_status_ptr_t __attribute__ ((__transparent_union__));
pid_t wait (wait_status_ptr_t);
このインターフェースでは,int *型とunion wait *型の引き数どちらでも渡すことができます.
int w1 () { int w; return wait (&w); }
int w2 () { union wait w; return wait (&w); }
このインターフェースでは,waitの実装例は次のようになります.
pid_t wait (wait_status_ptr_t p)
{
return waitpid (-1, p.__ip, 0);
}
○unused
前項と同じように,この属性が型に対して指定されると,その型をもつ変数はおそらく使われないはずであるということを意味します.GNU Cは,その変数については警告メッセージを出力しません.
● Cの式をオペランドとしてもつアセンブラ命令
asmを使ったアセンブラ命令の中でオペランドをCの式を使って指定することができます.このことは,使いたいデータがどのレジスタまたはメモリ位置に保持されるのかを推測する必要がないということを意味しています.
マシン記述(machine description)の中で使われるものとよく似たアセンブラ命令テンプレートに加えて,個々のオペランドのoperand constraint stringを指定しなければなりません.
拡張されたアセンブラ命令の構文は次のとおりです.
asm (アセンブリコード : 出力オペランド : 入力オペランド
: 保持されないレジスタ);
○operand constraint stringについて
出力オペランドや入力オペランド中で,それに続く括弧でくくった式に,どのようなレジスタやメモリを割り当てるかを決めます.この概略は以下に記します.
○アセンブリコードについて
内容が破損してしまうレジスタを3番目の : の後に書いておくと,レジスタの退避,復帰のためのコードを自動的に生成してくれます.プログラマはレジスタの退避,復帰を省いて書くことができます.
○operand constraint stringについての説明
x86系でよく使われる制約は次のとおりです.
“r”:そのオペランドがレジスタでなくてはならないことを表します
“f”:浮動小数点レジスタでなくてはならないことを表します
“m”:メモリオペランドでなくてはならないことを表します
“g”:メモリでもレジスタでも構わないときにこれを指定します
“q”:EAX,EBX,ECX,EDXレジスタに割り当てることを意味します
“a”,“b”,“c”,“d”,“D”,“S”,“B”:
それぞれ,EAX,EBX,ECX,EDX,EDI,ESI,EBPの各レジスタに割り当てることを意味します
“0”,“1”,……:
これらの数字は入力オペランドのみに指定できます.出力オペランド%0や%1と同一であることをコンパイラに教えることができます
例として単純な足し算を行うプログラムソースをリスト28,リスト29に掲載します.
〔リスト28〕asm命令のoperand constraint string概略を説明するCソース(test105.c)
|
#include <stdio.h>
//Cの式をオペランドとして持つアセンブラ命令
int main(void)
{
int x = 100;
int y = 500;
int result;
asm("nop");
//#割り当て場所はメモリでもレジスタでも構わない設定
asm("movl %0, %%eax" :: "g"(x): "ax");
asm("movl %0, %%ebx" :: "g"(y): "bx");
asm("addl %%ebx, %%eax" ::: "ax","bx");
asm("movl %%eax, %0" : "=g"(result) :: "ax");
asm("nop");
//割り当て場所はレジスタである
asm("movl %0, %%eax" :: "r"(x): "ax");
asm("movl %0, %%ebx" :: "r"(y): "bx");
asm("addl %%ebx, %%eax" ::: "ax","bx");
asm("movl %%eax, %0" : "=r"(result) :: "ax");
asm("nop");
//割り当て場所はメモリである
asm("movl %0, %%eax" :: "m"(x): "ax");
asm("movl %0, %%ebx" :: "m"(y): "bx");
asm("addl %%ebx, %%eax" ::: "ax","bx");
asm("movl %%eax, %0" : "=m"(result) :: "ax");
asm("nop");
//割り当て場所はEAX, EBX, ECX, EDXレジスタのいずれか
asm("movl %0, %%eax" :: "q"(x): "ax");
asm("movl %0, %%ebx" :: "q"(y): "bx");
asm("addl %%ebx, %%eax" ::: "ax","bx");
asm("movl %%eax, %0" : "=q"(result) :: "ax");
asm("nop");
//
asm("movl %0, %%eax" :: "D"(x): "ax"); //EDIレジスタに割り当て指定
asm("movl %0, %%ebx" :: "S"(y): "bx"); //ESIレジスタに割り当て指定
asm("addl %%ebx, %%eax" ::: "ax","bx");
asm("movl %%eax, %0" : "=c"(result) :: "ax"); //ECXレジスタに割り当て指定
asm("nop");
printf("%d\n",result);
return 0;
}
|
|
〔リスト29〕asm命令のoperand constraint string概略を説明するCソースから生成したアセンブラ(test105.s)
|
.file "test105.c"
.version "01.01"
gcc2_compiled.:
.section .rodata
.LC0:
.string "%d\n"
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $28,%esp
pushl %edi
pushl %esi
pushl %ebx
movl $100,-4(%ebp)
movl $500,-8(%ebp)
#APP
#割り当て場所はメモリでもレジスタでも構わない設定
nop
movl -4(%ebp),%eax
movl -8(%ebp),%ebx
addl %ebx,%eax
movl %eax,-12(%ebp)
nop
#NO_APP
movl -4(%ebp),%edx
#APP
#割り当て場所はレジスタである
movl %edx,%eax
#NO_APP
movl -8(%ebp),%edx
#APP
movl %edx,%ebx
addl %ebx,%eax
movl %eax,%edx
#NO_APP
movl %edx,%eax
movl %eax,-12(%ebp)
#APP
nop
#割り当て場所はメモリである
movl -4(%ebp),%eax
movl -8(%ebp),%ebx
addl %ebx,%eax
movl %eax,-12(%ebp)
nop
#NO_APP
movl -4(%ebp),%edx
#APP
#割り当て場所はEAX, EBX, ECX, EDXレジスタのいずれか
movl %edx,%eax
#NO_APP
movl -8(%ebp),%edx
#APP
movl %edx,%ebx
addl %ebx,%eax
movl %eax,%edx
#NO_APP
movl %edx,%eax
movl %eax,-12(%ebp)
#APP
nop
#NO_APP
movl -4(%ebp),%edi
#APP
movl %edi,%eax #EDIレジスタに割り当て指定
#NO_APP
movl -8(%ebp),%esi
#APP
movl %esi,%ebx #ESIレジスタに割り当て指定
addl %ebx,%eax
movl %eax,%ecx #ECXレジスタに割り当て指定
#NO_APP
movl %ecx,%eax
movl %eax,-12(%ebp)
#APP
nop
#NO_APP
addl $-8,%esp
movl -12(%ebp),%eax
pushl %eax
pushl $.LC0
call printf
addl $16,%esp
xorl %eax,%eax
jmp .L2
.p2align 4,,7
.L2:
leal -40(%ebp),%esp
popl %ebx
popl %esi
popl %edi
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
これらのリストのとおり,制約を指定するとそのとおりに変数の割り当てを行います.
なお,これはIntel 386系に関する情報です.ほかのプロセッサについても,いろいろなoperand constraint stringがあります.ARMファミリ,AMD 29000ファミリ,IBM RS6000,Intel 960,MIPS,Motorola 680x0,SPARC……以上のプロセッサ特有の設定があるので,詳細はGCCマニュアルを参照してください.
|