gcc 內嵌彙編的學習筆記 I
--第一個混合編碼的加法計算機
作者:ShellEx.
ShellEx.cn && blog.csdn.net/shellex 著作權
寫了一段簡單的代碼:
#include <stdio.h>
int main() {
int in1 = 0, in2 = 0, out = 0;
printf("PLZ input 2 Number like this: (x1 + x2) /n");
scanf("%d + %d", &in1, &in2);
asm volatile(
"add %1, %0/n/t"
"add %2, %0/n/t"
"nop/n/t"
:"=r"(out)
:"r"(in1),"r"(in2)
:
);
printf("%d + %d = %d./n", in1, in2, out);
return 0;
}
///////////////////////////////////////////////////////////////
輸入兩數,求他們的和。心想估計沒有問題,就編譯運行。
運行結果:
PLZ input 2 Number like this: (x1 + x2)
2+3
2 + 3 = 10.
暈一個,和我想的不一樣啊。去看看彙編代碼吧(gcc加上-S參數可以生
成彙編源檔案):
...
movl $0, -4(%ebp)
movl $0, -8(%ebp)
movl $0, -12(%ebp)
...
movl -4(%ebp), %edx
movl -8(%ebp), %eax
/APP
add %edx, %eax
add %eax, %eax
nop
/NO_APP
movl %eax, -12(%ebp)
movl -12(%ebp), %eax
...
///////////////////////////////////////////////////////////////
以上代碼僅僅關鍵區段,前後有省略。在/APP和/NO_APP之間的是內嵌匯
編部分。可以看到,gcc先把-4(%ebp)和-8(%ebp)(分別是in1和in2)
放入%edx和%eax寄存器,然後執行第一個add使得eax儲存有兩個數的和,
第二個add又把eax加在自己身上,最後把eax的值賦給-12(%ebp)也就是
out.功能大約相當於下面的C++代碼:
tmp1 = in1;
tmp2 = in2;
tmp1 += tmp2;
tmp1 += tmp1;
out = tmp1;
結果當然不對啦。這是由於gcc不知道錯誤地使用eax寄存器造成的。他並
不知道%2和%0用了同一個寄存器呢。所以我在輸出部分加入"&"限制符。
asm volatile(
"add %1, %0/n/t"
"add %2, %0/n/t"
"nop/n/t"
:"=&r"(out)
:"r"(in1),"r"(in2)
:
);
///////////////////////////////////////////////////////////////
編譯執行:
PLZ input 2 Number like this: (x1 + x2)
2+3
2 + 3 = 2009143897.
又出現了奇怪的問題。再看看彙編代碼:
movl -4(%ebp), %edx
movl -8(%ebp), %eax
/APP
add %edx, %ecx
add %eax, %ecx
nop
/NO_APP
movl %ecx, %eax
movl %eax, -12(%ebp)
///////////////////////////////////////////////////////////////
貌似沒問題,其實不然。存放結果的ecx寄存器並沒有一開始就把out
的值讀入(不像in1 和 in2,被讀入edx和eax)。說明gcc對於使用"r"
限制符的輸出變數,並沒有像對待輸入變數那樣分配完寄存器後就讀入
值。資料表明,這個行為可能是由於AT&T彙編源於CISC架構處理器的匯
編語言,在CISC架構處理器中大部分指令輸入輸出明顯分開,而不像
RISC架構處理器那樣一個運算元可以輸出也可以輸入。
所以我應該讓gcc知道,我的out也應該享受同等待遇。
#include <stdio.h>
int main() {
int in1 = 0, in2 = 0, out = 0;
printf("PLZ input 2 Number like this: (x1 + x2) /n");
scanf("%d + %d", &in1, &in2);
asm volatile(
"add %1, %0/n/t"
"add %2, %0/n/t"
"nop/n/t"
:"=&r"(out)
:"r"(in1),"r"(in2),"0"(out)
:
);
printf("%d + %d = %d./n", in1, in2, out);
return 0;
}
///////////////////////////////////////////////////////////////
在輸入部分添加對out的描述,並且用限定符"0"指定out就是輸入部分的
那個%0.因為gcc是不會去判斷%1和%3是否關聯自同一個變數的。所以需要
顯性指定。
彙編代碼如下。
movl -4(%ebp), %ecx
movl -8(%ebp), %edx
movl -12(%ebp), %eax
/APP
add %ecx, %eax
add %edx, %eax
nop
/NO_APP
movl %eax, -12(%ebp)
///////////////////////////////////////////////////////////////
編譯啟動並執行輸出也是正確的了:
PLZ input 2 Number like this: (x1 + x2)
2+3
2 + 3 = 5.
但是這麼寫真是麻煩。其實有個更簡單的方法,就是使用"+"限定符。表
示運算元可以讀和寫。
asm volatile(
"add %1, %0/n/t"
"add %2, %0/n/t"
"nop/n/t"
:"+r"(out)
:"r"(in1),"r"(in2)
:
);
///////////////////////////////////////////////////////////////
彙編代碼如下:
movl -12(%ebp), %edx
movl -4(%ebp), %ecx
movl -8(%ebp), %eax
/APP
add %ecx, %edx
add %eax, %edx
nop
/NO_APP
movl %edx, -12(%ebp)
///////////////////////////////////////////////////////////////
沒想到才剛剛開始就遇到這麼多問題,記錄下來,就算是學習筆記。既然
是筆記,如果看官們發現錯誤,請一定不吝賜教。謝謝觀賞。
shellex.cn && blog.csdn.net/shellex 著作權