cとアセンブリの混合プログラミング
様々な高級言語が盛んな今日、なぜアセンブリを使うのでしょうか.実は主な原因はあります:第一に、C言語で肝心な場所にアセンブリを埋め込むと、いくつかの肝心なアルゴリズムなど、最大の性能向上を得ることができます.第二に、ハードウェア関連の機能を実現する(この点は組み込み開発でよく用いられる).第三に、C言語で実現できない特性をアセンブリで実現することができ、例えばlock命令を用いて原子操作を実現することができる.本稿では、アセンブリ言語をc言語に埋め込む基礎を紹介し、2つの例を示した.
小宇のブログ
データベースでは、tasロックなどの特殊な操作を実現するために、アセンブリを使用して完了する必要があります.例えばPostgreSQLのtasロックはアセンブリで書かれています.では、どのようにcとアセンブリを混合してプログラミングしますか?まず基本文法を紹介します.c言語にアセンブリを埋め込む文法は以下の通りです.
以下、上記の内容をそれぞれご紹介します
AssemblerTemplate
アセンブリテンプレートは実際には文字列です.たとえば
中には
asmSymbolicName
constraint
1つの文字列を定義記号として使用します.定義子は変数配置の位置を説明するために使用する
cvariablename
cプログラムコンテキストの変数名を参照
InputOperands
文字通り
Clobbers
では、基本的な文法を説明した後、いくつかの例を簡単に見てみましょう.
例1-代入
最も簡単な例として、アセンブルされた
結果は,a,bが既に付与されていることがわかる.
実際に生成されたアセンブリ:
例2–交換
一般的にc言語では、a,bの値を交換するには3つの文が必要です.
アセンブリ
結果は,a,bが既に付与されていることがわかる.
例3-tasロック
tasロックは、操作全体が1つの命令でのみ完了し、操作の「原子性」を保証するためにバスをロックする.それに比べて、C文はコンパイル後にいったいいくつかの命令が保証されていないのか、計算中にバスにロックをかけることも要求できない.このときは
上記コードの
興味があればさらに読むことができます:gcc公式ドキュメント:https://gcc.gnu.org/onlinedocs/gcc-6.2.0/gcc/Extended-Asm.html#Extended-ASM Linuxアセンブリ言語開発ガイド:http://www.ibm.com/developerworks/cn/linux/l-assembly良いブログ:http://blog.csdn.net/hu3167343/article/details/37660593
小宇のブログ
データベースでは、tasロックなどの特殊な操作を実現するために、アセンブリを使用して完了する必要があります.例えばPostgreSQLのtasロックはアセンブリで書かれています.では、どのようにcとアセンブリを混合してプログラミングしますか?まず基本文法を紹介します.c言語にアセンブリを埋め込む文法は以下の通りです.
asm [volatile] ( AssemblerTemplate
: OutputOperands
[ : InputOperands
[ : Clobbers ] ])
asm
:埋め込みアセンブリの開始を示すvolatile
:コンパイラ最適化をオフにすることで、一部のコンパイラが無効とみなされるアセンブリを削除しないAssemblerTemplate
:アセンブリを生成するためのテンプレートで、アセンブリコードとプレースホルダが含まれているOutputOperands
:アセンブリで修正されたcプログラムの変数、カンマ区切りInputOperands
:アセンブリで読み取ったcプログラムの変数をカンマ区切りClobbers
:OutputOperandsにリストされている以外はアセンブリで修正されたregisterまたはその他の値以下、上記の内容をそれぞれご紹介します
AssemblerTemplate
アセンブリテンプレートは実際には文字列です.たとえば
" cmpb $0,%1
"
" jne 1f
"
" lock
"
" xchgb %0,%1
"
中には
OutputOperands
・InputOperands
への引用が含まれています.次にコンパイラは、アセンブリ魔板の参照などを実際のアセンブリ命令に変換します.アセンブルテンプレートにはアセンブル命令のほかに、特殊記号がいくつかあります.特殊記号は他の意味を表しています.%n
:引用OutputOperands
InputOperands
のいくつかの値%%
:アセンブルに出力%
記号%=
:1つの数字を出力します.毎回違います.一般的にgotoラベルとして使用されます.%{
:出力{
%|
:出力|
%}
:出力}
OutputOperands OutputOperands
の文法は以下の通り.[ [asmSymbolicName] ] constraint (cvariablename)
asmSymbolicName
asmSymbolicName
asm記号名、asmテンプレート内に%[Value]
で指定されている場合.では、OutputOperands
で[ [asmSymbolicName] ] constraint (cvariablename)
具体的なcプログラム変数名にマッピングできます.例: int a,b;
/* a=1;b=2;*/
asm("mov $1,%[a]
"
"mov $2,%[b]
"
:[a]"+rm"(a),[b]"+rm"(b)
:/* no output */
:"cc");
printf("a=%d
b=%d
",a,b);
asm
名称を使用していない場合は、0の下付きで始まる変数名%0
、%1
で参照してください.例: int a,b;
/* a=1;b=2;*/
asm("mov $1,%0
"
"mov $2,%1
"
:"+rm"(a),"+rm"(b)
:/* no output */
:"cc");
printf("a=%d
b=%d
",a,b);
constraint
1つの文字列を定義記号として使用します.定義子は変数配置の位置を説明するために使用する
=
この変数は既存の変数を上書きする+
変数は読み書き用r
変数をregisterレジスタに入れるm
変数をmemoryに置くcvariablename
cプログラムコンテキストの変数名を参照
InputOperands
文字通り
InputOperands
入力された変数を表し、これらの変数がアセンブリされて使用されます.Clobbers
cc
以上のアセンブラコードがフラグビットを変更しているmemory
以上のアセンブラコードがメモリ読み書きされているでは、基本的な文法を説明した後、いくつかの例を簡単に見てみましょう.
例1-代入
最も簡単な例として、アセンブルされた
mov
命令を用いて、値を付与する.#include
#include
int main()
{
int a,b;
__asm__("mov $1,%0
"
"mov $2,%1
"
:"+rm"(a),"+rm"(b)
:/* no output */
:"cc");
printf("a=%d
b=%d
",a,b);
}
結果は,a,bが既に付与されていることがわかる.
$ ./main
a=1
b=2
実際に生成されたアセンブリ:
0000000000400526 :
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
40052a: 48 83 ec 10 sub $0x10,%rsp
40052e: 8b 55 f8 mov -0x8(%rbp),%edx
400531: 8b 45 fc mov -0x4(%rbp),%eax
400534: ba 01 00 00 00 mov $0x1,%edx
400539: b8 02 00 00 00 mov $0x2,%eax
40053e: 89 55 f8 mov %edx,-0x8(%rbp)
400541: 89 45 fc mov %eax,-0x4(%rbp)
400544: 8b 55 fc mov -0x4(%rbp),%edx
400547: 8b 45 f8 mov -0x8(%rbp),%eax
40054a: 89 c6 mov %eax,%esi
40054c: bf f4 05 40 00 mov $0x4005f4,%edi
400551: b8 00 00 00 00 mov $0x0,%eax
400556: e8 a5 fe ff ff callq 400400 @plt>
40055b: b8 00 00 00 00 mov $0x0,%eax
400560: c9 leaveq
400561: c3 retq
400562: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
400569: 00 00 00
40056c: 0f 1f 40 00 nopl 0x0(%rax)
例2–交換
一般的にc言語では、a,bの値を交換するには3つの文が必要です.
temp=a;
a=b;
b=temp;
アセンブリ
xchg
命令により、1つの命令でa,b交換を完了し、中間変数を使用しない#include
#include
int main()
{
int a=2,b=1;
/* a=b;b=a;*/
asm("xchg %0,%1
"
:"+rm"(a),"+rm"(b)
:/* no output */
:"cc");
printf("a=%d
b=%d
",a,b);
}
結果は,a,bが既に付与されていることがわかる.
$ ./main
a=1
b=2
例3-tasロック
tasロックは、操作全体が1つの命令でのみ完了し、操作の「原子性」を保証するためにバスをロックする.それに比べて、C文はコンパイル後にいったいいくつかの命令が保証されていないのか、計算中にバスにロックをかけることも要求できない.このときは
lock
コマンドでこの作業を完了します.static __inline__ int
tas(volatile lock_t *lock)
{
register lock_t _res = 1;
__asm__ __volatile__(
" cmpb $0,%1
"
" jne 1f
"
" lock
"
" xchgb %0,%1
"
"1:
"
: "+q"(_res), "+m"(*lock)
: /* no inputs */
: "memory", "cc");
return (int) _res;
}
上記コードの
lock
命令は次のxchgb
命令交換*lock
および_res
の動作が原子的であることを確保している.これにより、取得*lock
の値を戻り値として、*lock
を1とする2つの動作が一気に完了します.興味があればさらに読むことができます:gcc公式ドキュメント:https://gcc.gnu.org/onlinedocs/gcc-6.2.0/gcc/Extended-Asm.html#Extended-ASM Linuxアセンブリ言語開発ガイド:http://www.ibm.com/developerworks/cn/linux/l-assembly良いブログ:http://blog.csdn.net/hu3167343/article/details/37660593