您好,登錄后才能下訂單哦!
這篇文章主要介紹了C指針原理之C內嵌匯編的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
內聯匯編的重要性體現在它能夠靈活操作,而且可以使其輸出通過 C 變量顯示出來。因為它具有這種能力,所以 "asm" 可以用作匯編指令和包含它的 C 程序之間的接口。簡單得說,內聯匯編,就是可以讓程序員在C語言中直接嵌入匯編代碼,并與匯編代碼交互C程序中的C表達式,享受匯編的高運行效率。
內聯匯編的格式是直接在C代碼中插入以下格式:
asm( .... .... )
其中的"..."為匯編代碼,比如下面例子中,在 result=a*b和printf("%d\n",result)之間插入一段匯編,
下面的這段匯編什么都不做,每個nop指令占用一個指令的執行時間
result=a*b; asm("nop\n\t" "nop\n\t" "nop\n\t" "nop");//4個nop指令,\n\t表示換行,然后加上TAB行首空,因為每個匯編指令必須在單獨一行,需要換行,加上制表符是為了適應某些編譯器的要求。 printf("%d\n",result);
可以很明顯地看到:
匯編代碼之間用“\n\t”間隔,并且每條匯編代碼單獨占用一行,共有4個nop指令,每個指令后的“\n\t”表示換行,然后加上TAB行首空,因為每個匯編指令必須在單獨一行,需要換行,加上制表符是為了適應某些編譯器的要求。
下面是一個完整的例子,內嵌的匯編完成對2個C程序定義的全局變量c和d的相加,并將相加結果存入全局變量addresult中:
#include <stdio.h> int c=10; int d=20; int addresult; int main(void){ int a=6; int b=2; int result; result=a*b; asm("nop\n\t" "nop\n\t" "nop\n\t" "nop");//4個nop指令,\n\t表示換行,然后加上TAB行首空,因為每個匯編指令必須在單獨一行,需要換行,加上制表符是為了適應某些編譯器的要求。 printf("%d\n",result); asm("pusha\n\t" "movl c,%eax\n\t" "movl d,%ebx\n\t" "add %ebx,%eax\n\t" "movl %eax, addresult\n\t" "popa");//使用全局C變量c和d printf("%d\n",addresult); return 0; }
編譯上述代碼
$ gcc -o test test.c $ ./test 12 30
在匯編代碼中可以直接使用變量名稱操作C程序定義的全局變量,比如c、d和addresult就是全局變量:
"movl c,%eax\n\t" "movl d,%ebx\n\t" "movl %eax, addresult\n\t"
內聯匯編部分如果不需要編譯器優化( 優化可能破壞匯編代碼的內部結構,因為匯編代碼直接操作寄存器,而寄存器使用優化是編譯器提供的功能), 可以在 "asm" 后使用關鍵字 "volatile"。
asm volatile( .... .... )
如果程序必須與 ANSI C 兼容,則應該使用 asm 和 volatile。
__asm__ __volatile__( ......... ......... )
下面的代碼和剛才代碼功能一樣,唯一的區別是不需要優化
#include <stdio.h> int c=10; int d=20; int addresult; int main(void){ int a=6; int b=2; int result; result=a*b; //ansi c標準的asm有其它用,所以用__asm__,__volatile__表示內聯匯編部分不用優化(可以用volatile,但是ansi c不行),以防優化破壞內聯代碼組織結構 __asm__ __volatile__("nop\n\t" "nop\n\t" "nop\n\t" "nop");//4個nop指令,\n\t表示換行,然后加上TAB行首空,因為每個匯編指令必須在單獨一行,需要換行,加上制表符是為了適應某些編譯器的要求。 printf("%d\n",result); __asm__ __volatile__("pusha\n\t" "movl c,%eax\n\t" "movl d,%ebx\n\t" "add %ebx,%eax\n\t" "movl %eax, addresult\n\t" "popa");//使用全局C變量c和d printf("%d\n",addresult); return 0; }
如何在內聯匯編中訪問C程序的局部變量呢,請看下面這段代碼。
#include <stdio.h> int main(void){ //不使用全局變量,必須使用擴展GNU的asm //格式為:asm("匯編代碼":輸出位置:輸入位置:改動的寄存器列表) //a為eax,ax,al;b為ebx等;c為ecx等;d為edx等;S為esi或si;D為edi或di //+讀和寫;=寫;%如果必要,操作數可以和下一個操作數切換;&在內聯函數完成之前,可以刪除或重新使用操作數 int xa=6; int xb=2; int result; //ansi c標準的asm有其它用,所以用__asm__,__volatile__表示內聯匯編部分不用優化(可以用volatile,但是ansi c不行),以防優化破壞內聯代碼組織結構 asm volatile( "add %%ebx,%%eax\n\t" "movl $2,%%ecx\n\t" "mul %%ecx\n\t" "movl %%eax,%%edx" :"=d"(result):"a"(xa),"b"(xb):"%ecx");//注意擴展方式使用2個%表示 printf("%d\n",result); return 0; }
這個例子完成這個計算:(xa+xb)2=(6+2)2=16
不使用全局變量與匯編代碼交互,我們必須使用擴展GNU的asm ,格式為:
asm("匯編代碼":輸出位置:輸入位置:改動的寄存器列表)
匯編代碼中涉及寄存器部分的使用2個“%”,如:使用%%eax表示eax寄存器
輸出位置、輸入位置的特殊命名規則為:
a為eax,ax,al;b為ebx等;c為ecx等;d為edx等;S為esi或si;D為edi或di
+讀和寫
=寫
%如果必要,操作數可以和下一個操作數切換
&在內聯函數完成之前,可以刪除或重新使用操作數
上述代碼中,匯編代碼部分為
輸出位置、輸入位置、改動的寄存器列表部分為:
:"=d"(result):"a"(xa),"b"(xb):"%ecx"
先來看匯編代碼部分,使用雙%號表示寄存器,比如:
"add %%ebx,%%eax\n\t"
關于輸出位置、輸入位置部分,可以這么理解:將變量與寄存器綁定,綁定后,對寄存器的操作就是對變量的操作。
:"=d"(result):"a"(xa),"b"(xb)
將result與寄存器edx綁定,xa與寄存器eax綁定,xb與寄存器ebx綁定。
%ecx屬于需要改動的寄存器
#include <stdio.h> int main(void){ int xa=6; int xb=2; int result; //使用占位符,由r表示,編譯器自主選擇使用哪些寄存器,%0,%1。。。表示第1、2。。。個變量 asm volatile( "add %1,%2\n\t" "movl %2,%0" :"=r"(result):"r"(xa),"r"(xb)); printf("%d\n",result); return 0; }
result、xa、xb綁定的寄存器由編譯器決定,前面的例子中我們采用直接指定的方式,在這里我們改成由編譯器
自主選擇,"r"是占位符,表示由編譯器自主選擇使用哪些寄存器,不指定哪個變量綁定在哪個寄存器上,
:"=r"(result):"r"(xa),"r"(xb)
那我們如何知道這些變量綁定在哪些寄存器上呢,不知道綁定的寄存器,如何對變量進行操作呢,可以使用
%0,%1這樣的符號來代替要操作的寄存器,%后的數字表示第幾個變量,如:%0,%1。。。表示第1、2。。。個變量。
:"=r"(result):"r"(xa),"r"(xb)
上面這個輸出和輸入列表已經指定了變量的順序,
result是第0個,xa是第1個,xb是第2個
下面的例子完成 xb=xb-xa的計算,問題出現了,可能會導致xb被分配了2個寄存器:
:"=r"(xb):"r"(xa),"r"(xb));
使用引用占位符能有效地使用可用寄存器,在這里我們指定xb使用第0個變量綁定的寄存器
:"=r"(xb):"r"(xa),"0"(xb));
第0個變量就是xb,即xb綁定的寄存器被修改后,結果仍寫回原寄存器
下面是完整例子
#include <stdio.h> int main(void){ int xa=2; int xb=6; asm volatile( "subl %1,%0\n\t" :"=r"(xb):"r"(xa),"0"(xb)); printf("%d\n",xb); return 0; }
我們編譯運行一下
$ gcc -o test test.c $ ./test 4
用數字來表示變量的順序也許很麻煩,我們可以使用更簡單的方法,使用“[標識]”的格式標記綁定后的變量。 下面的代碼完成xb=xb+xa的計算
#include <stdio.h> int main(void){ int xa=6; int xb=2; asm volatile( "add %[mya],%[myb]\n\t" :[myb]"=r"(xb):[mya]"r"(xa),"0"(xb)); printf("%d\n",xb); return 0; }
我們使用m標記可以直接在內存中對數進行操作,前面的例子對變量進行操作時都需要將變量值存儲在要修改的寄存器中,然后將它寫回內存位置中.
#include <stdio.h> int main(void){ int xa=2; int xb=6; asm volatile( "subl %1,%0\n\t" :"=r"(xb):"m"(xa),"0"(xb)); printf("%d\n",xb); return 0; }
我們直接從xa的內存地址中將xa取出,而不需要再將xa先存儲在一個寄存器。
首先,我們看一下AT&T匯編各段的意義
節 含義
.text 已編譯程序的機器代碼
.rodata 只讀數據,如pintf和switch語句中的字符串和常量值
.data 已初始化的全局變量
.bss 未初始化的全局變量
.symtab 符號表,存放在程序中被定義和引用的函數和全局變量的信息
.rel.text 當鏈接器吧這個目標文件和其他文件結合時,.text節中的信息需修改
.rel.data 被模塊定義和引用的任何全局變量的信息
.debug 一個調試符號表。
.line 原始C程序的行號和.text節中機器指令之間的映射
.strtab 一個字符串表,其內容包含.systab和.debug節中的符號表
上面列表也許比較抽象,我們從一個C程序生成的中間匯編代碼分析:
#include <stdio.h> void main(){ char *x="xxxx"; char y[]="yy";//y的16進制ASCII碼是97,9797的十進制為31097 printf("%s-----%s",x,y); exit(0); }
我們使用gcc -S testcr.c,查看編譯生成的匯編代碼(為便于理解,將生成的匯編代碼進行了注釋)
.file "testcr.c" .section .rodata .LC0: .string "xxxx"#使用char *分配 .LC1: .string "%s-----%s" .text .globl main .type main, @function main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $32, %esp#分配32字節棧空間,根據變量情況分配 movl $.LC0, 24(%esp)#x變量使用指針(4個字節大小),放入棧中,可以看到,變量分配靠近棧空間的尾部 movw $31097, 29(%esp)#字符'yy'移到main程序的棧中,直接將y變量的值放入棧中 movb $0, 31(%esp)#加上NULL標志,表示字符結束 movl $.LC1, %eax leal 29(%esp), %edx movl %edx, 8(%esp) movl 24(%esp), %edx movl %edx, 4(%esp) movl %eax, (%esp) call printf movl $0, (%esp) call exit .size main, .-main .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" .section .note.GNU-stack,"",@progbits
在MAIN函數中char *分配在只讀數據段中,實際使用時,只在程序棧中分配一個指針的空間。char[] 在程序棧中分配空間,然后直接使用movl、movw之類的匯編直接把值放入棧中空間。那么在其它函數中聲明的呢,可以從以下程序中看出,仍然如此。
#include <stdio.h> void myprinf(){ char *x="xxxx"; char y[]="yy";//y的16進制ASCII碼是97,9797的十進制為31097 printf("%s-----%s",x,y); } void main(){ int num=1; myprint(); exit(0); }
生成的中間匯編代碼為:
.file "testcr.c" .section .rodata .LC0: .string "xxxx" .LC1: .string "%s-----%s" .text .globl myprinf .type myprinf, @function myprinf: pushl %ebp movl %esp, %ebp subl $40, %esp movl $.LC0, -16(%ebp) movw $31097, -11(%ebp) movb $0, -9(%ebp) movl $.LC1, %eax leal -11(%ebp), %edx movl %edx, 8(%esp) movl -16(%ebp), %edx movl %edx, 4(%esp) movl %eax, (%esp) call printf leave ret .size myprinf, .-myprinf .globl main .type main, @function main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $32, %esp movl $1, 28(%esp) call myprint movl $0, (%esp) call exit .size main, .-main .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" .section .note.GNU-stack,"",@progbits
內存的常用分配方式有:
第一,靜態分配,所有名字在編譯時綁定某個存儲位置。不能在運行時改變
第二,棧分配,活動時壓入系統棧。
第三,堆分配,以任意次序分配
感謝你能夠認真閱讀完這篇文章,希望小編分享的“C指針原理之C內嵌匯編的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。