⑴ 如何把匯編程序嵌入到c語言中
不同編譯器嵌入匯編的方式不一樣,具體如下:
一、Turbo
C ,
也就是所說的TC。
1、使用預處理程序的偽指令#asm和#endasm,#asm用來開始一個匯編程序塊,而#endasm指令用於該塊的結束。
參考代碼:
int mul(int a, int b)
{
/*匯編開始*/
#asm
mov ax,word ptr 8[bp]
imul ax word ptr 10[bp]
#endasm
/*匯編結束。*/
}
2、使用asm語句:
格式:asm<匯編語句>
參考代碼:
int mul(int a, int b)
{
asm mov ax,word ptr 8[bp]
asm imul ax word ptr 10[bp]
/*
每個asm對應一句匯編
注意結尾不需要分號
*/
}
二、VC++/VS
格式:
__asm
匯編指令
[
;
]
__asm
{
匯編指令
}
[
;
]
asm前面是兩條下劃線,後面的方括弧內容表示分號可有可無。
使用方法:
1、一條一條地用:
__asm mov al, 2
__asm mov dx, 0xD007
__asm out dx, al
每行一條匯編,
可以有分號,也可以沒有。
2、組成一塊地用:
__asm {
mov al, 2
mov dx, 0xD007
out dx, al
}
整體作為一個匯編代碼塊。
3、也可以將多條匯編寫在一行:
__asm mov al, 2 __asm mov dx, 0xD007 __asm out dx, al
三、GNU
GCC
GCC對匯編的支持是最豐富的,簡單介紹如下:
1、
用到的關鍵字:
「__asm__」
表示後面的代碼為內嵌匯編,「asm」是「__asm__」的別名。
「__volatile__」 表示編譯器不要優化代碼,後面的指令保留原樣,「volatile」是它的別名。
括弧裡面是匯編指令。
內嵌匯編語法如下:
__asm__(
匯編語句模板:
輸出部分:
輸入部分:
破壞描述部分)
一個簡單的匯編模板:
int a=10,b;
asm("movl %1, %%eax;
movl %%eax, %0;"
:"=r"(b) /*輸出部*/
:"r"(a) /*輸入部*/
:"%eax" /*毀壞部*/
);
表示C語言里的「b=a;」。
里邊r表示使用任意寄存器,%0、%1表示使用兩個寄存器,一般只能%0~%9共十個操作數,按輸入輸出部變數出現順序進行映射。
寄存器用兩個百分號,是因為使用了%0%1這些數字使百分號有了特殊意義,所以在操作數出現的寄存器必須用雙百分表示。
毀壞部里邊的%eax表示eax寄存器在匯編代碼塊執行過程中會被改寫,在執行前要保護好,這是提交給編譯器決定的。
⑵ c語言嵌入的匯編語句如何使用C語言定義的變數
直接用即可,比如:
⑶ C語言內嵌匯編
在調試聯盛德W800晶元代碼時,看到了下面的代碼實現,記錄下C語言內嵌匯編語言的實現方式。部分代碼如下:
該函數實現功能:將入參newMask的值寫入到psr寄存器,用於恢復現場。
代碼下載路徑:http://www.winnermicro.com/html/1/156/158/558.html
__asm__ (
匯編語句部分
:輸出部分
:輸入部分
:破壞描述部分
);
1)C內嵌匯編以關鍵字 __asm__ 或 asm 開始。如果使用 volatile 關鍵字,則表示告訴編譯器不優化後續的代碼。
2) 匯編語句部分:可以包含多條匯編語句,每條語歲碰句之間用「 」或「;」,例:
註:匯編語句中的操作數可以使用佔位符引用C語言變數,名稱如下:%0,%1,…。
2) 輸出部分:在匯編語言中被修改的C變數
3) 輸入部分:作為參數輸入到匯編語言中的C變數
註:輸出和輸入部分是針對匯編語句部分的輸入和輸出參數而言。例:如果匯編語言使用變數設置寄存器歷雀做,則用輸入部分;如果匯編語言從寄存器讀取值,保存到變數,則用輸出部分。
4) 破壞描述部分:通知編譯器使用了哪些寄存器或內存。
5) 每個部分之間用」:」隔開。匯編語句部分必不可少,其他部分可以肢衡省略。例: asm("nop") ,實現空操作。
⑷ C51單片機編程中C語言如何嵌套匯編語言
#include <reg52.h> #include <intrins.h> #define uchar unsigned char #define uint unsigned int sbit DS=P3^3; //定義DS18B20介面 int temp; uchar flag1; void display(unsigned char *lp,unsigned char lc);//數字的顯示函數;lp為指向數組的地址,lc為顯示的個數 void delay();//延時子函數,5個空指令 code unsigned char table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef,0x40,0x39,0x00};//共陰數碼管顯示數的組成是 "0-9""0-9有小數點的" "-" "C" "空表" unsigned char l_tmpdate[8]={0,0,10,0,0,0,0,0};//定義數組變數,並賦值1,2,3,4,5,6,7,8,就是本程罩尺序顯示的八個數 int tmp(void); void tmpchange(void); void tmpwritebyte(uchar dat); uchar tmpread(void); bit tmpreadbit(void); void dsreset(void); void delayb(uint count); void main() //主函數 { uchar i; int l_tmp; while(1) { tmpchange(); //溫擾悶坦度轉換 l_tmp=tmp(); //讀取溫度值 if(l_tmp<0) l_tmpdate[0]=20; //判斷溫度為負溫度,前面加"-" else { l_tmpdate[0]=l_tmp/1000; //顯示百位,這里用1000,是因為我們之前乖以10了 if(l_tmpdate[0]==0) l_tmpdate[0]=22;//判斷溫度為正溫度且沒有上百,前面不顯示,查表第12是空 } l_tmp=l_tmp%1000; l_tmpdate[1]=l_tmp/100;//獲取十位 l_tmp=l_tmp%100; l_tmpdate[2]=l_tmp/10;//獲取個位再 l_tmpdate[2]+=10;//加入小數點,查表可得出有小數點的排在後10位,所以加10 l_tmpdate[3]=l_tmp%10;//獲取小數第一位 l_tmpdate[4]=21; for(i=0;i<10;i++){ //循環輸出10次,提高亮度 display(l_tmpdate,5); } } } void display(unsigned char *lp,unsigned char lc)//顯示 { unsigned char i; //定義變數 P2=0; //埠2為輸出 P1=P1&0xF8; //將緩桐P1口的前3位輸出0,對應138譯門輸入腳,全0為第一位數碼管 for(i=0;i<lc;i++){ //循環顯示 P2=table[lp[i]]; //查表法得到要顯示數字的數碼段 delay(); //延時5個空指令 if(i==7) //檢測顯示完8位否,完成直接退出,不讓P1口再加1,否則進位影響到第四位數據 break; P2=0; //清0埠,准備顯示下位 P1++; //下一位數碼管 } } void delay(void) //空5個指令 { _nop_();_nop_();_nop_();_nop_();_nop_(); } void delayb(uint count) //delay { uint i; while(count) { i=200; while(i>0) i--; count--; }
⑸ IAR環境下 C語言中怎麼嵌入匯編寫延時程序
(1)、C51語言中調用匯編語言程序;
C51語言調用匯編語言程序要注意以下幾點:
1:在文件欄選中File Group和C51程序原文件,在配置文件選項中激活「產生匯編(SRC)文件,「編譯(SRC)文件」和「創建工程(目標)時包含」三個選項。
2:根據選擇的編譯模式,把相應的庫文件(如SMALL模式,庫文件為KEIL\C51\LIB\C51S.LIB)加入工程中。
3:在C51語言中必須聲明需要調用的函數為外部函數。extern void DELAY(void);
4:在匯編語言程序中必須聲族桐明被調用子程序為公共子程序,在被調用的文件中還需要聲明此文件是可從新定位的。
PUBLIC DELAY,DEL
DELAYY SEGMENT CODE
RSEG DELAYY
實例如下:
C51語言文件:
#include "reg51.h"
extern void DELAY(void);
extern void DEL(void);
void main(void)
{
P1=0x00;
DELAY();
DEL();
P1=0xff;
}
匯編語言文件:
PUBLIC DELAY,DEL
DELAYY SEGMENT CODE
RSEG DELAYY
DELAY: MOV R2,#3H
DJNZ R2,$
RET
DEL: MOV R3,#03H
DJNZ R3,$
RET
END
(2)、C51語言中嵌入匯編程序;
在C51語言中嵌套使用匯編語言編寫程序要注意以下的幾個問題:
1:在文件欄選中File Group和C51程序原文件,在配置文件選項中激活「產生匯編(SRC)文件,「編譯(SRC)文件」和「創建工程(目標)時包含」三個選項搭塌。
2:根據選擇的編譯模式,把相應的庫文件(如SMALL模式,庫文件為兆枝坦KEIL\C51\LIB\C51S.LIB)加入工程中。
3:用#pragma asm,和#pragma endasm語句包含嵌入的匯編語言程序。
實例如下:
#include "reg51.h"
void delay(void);
void main(void)
{
void delay(void);
P1=0x00;
#pragma asm
MOV R3,#08H
DJNZ R3,$
#pragma endasm
P1=0xff;
}
void delay(void)
{
#pragma asm
MOV R4,#08H
DJNZ R4,$
#pragma endasm
}
⑹ 如何把匯編程序嵌入到C語言中
這么寫:
__asm
{
//這里是匯編代碼
}
把匯局搜編代碼寫在asm括弧裡面即可,要注桐型歷意不要破壞堆棧的平衡。如果是編寫在windows上跑的程序,建議用win32匯租轎編。
⑺ 在c語言(C++或G++)中如何嵌入匯編
今天有點時間,重新改下了下,為避免因編譯器和平台實現而出現的問題,我寫了三個版本,分別是轎彎windows下vc6.0,windows下數前mingw和cygwin和linux下的gcc/g++。
vc6.0:
#include <stdio.h>
const char* input = "%d";
const char* output = "%d\n";
int n;
int main()
{
__asm
{
lea eax, n
push eax
push input
loopx:
call scanf
cmp eax, 1
jne end
mov ecx, n
jecxz end
dec ecx
push ecx
push output
call printf
add esp, 8
jmp loopx
end:
add esp, 8
}
return 0;
}
mingw/cygwin:
#include <stdio.h>
const char* input = "%d";
const char* output = "%d\n";
int n;
int main()
{
__asm__
(
"loop: \n"
"pushl $_n \n"
"pushl _input \n"
"call _scanf \n"
"addl $8, %esp \n"
"cmpl $1, %eax \n"
"jne end \n"
"movl _n, %ecx \n"
"jecxz end \n"
"decl %ecx \n"
"pushl %ecx \n"
"pushl _output \n"
"call _printf \n"
"addl $8, %esp \n"
"jmp loop \n"
"end:"
);
return 0;
}
linux gcc/g++:
#include <stdio.h>
const char* input = "%d";
const char* output = "%d\n";
int n;
int main()
{
__asm__
(
"pushl $n \n"
"pushl input \n"
"loop: \n"
"call scanf \n"
"cmp $1, %eax \n"
"jne end \n"
"movl n, %ecx \n"
"jecxz end \n"
"decl %ecx \n"
"薯帆清pushl %ecx \n"
"pushl output \n"
"call printf \n"
"addl $8, %esp \n"
"jmp loop \n"
"end: \n"
"addl $8, %esp \n");
return 0;
}
⑻ C語言中怎麼嵌入匯編
在 Visual C++ 中使用內聯匯編- -
使用內聯匯編可以在 C/C++ 代碼中嵌入匯編語言指令,帶衫而且不需要額外的匯編和連接步驟。在 Visual C++ 中,內聯匯編是內置的編譯器,因此不需要配置諸如 MASM 一類的獨立匯編工具。這里,我們就以 Visual Studio .NET 2003 為背景,介紹在 Visual C++ 中使用內聯匯的相關知識(如果是早期的版本,可能會有些許出入)。
內聯匯編代碼可以使用 C/C++ 變數和函數,因此它能非常容易地整合到 C/C++ 代碼中。它能做一些對於單獨使用 C/C++ 來說非常笨重或不可能完成的任務。
一、 優點
使用內聯匯編可以蔽碧在 C/C++ 代碼中嵌入匯編語言指令,而且不需要額外的匯編和連接步驟。在 Visual C++ 中,內聯匯編是內置的編譯器,因此不需要配置諸如 MASM 一類的獨立匯編工具。這里,我們就以 Visual Studio .NET 2003 為背景,介紹在 Visual C++ 中使用內聯匯的相關知識(如果是早期的版本,可能會有些許出入)。
內聯匯編代碼可以使用 C/C++ 變數和函數,因此它能非常容易地整合到 C/C++ 代碼中。它能做一些對於單獨使用 C/C++ 來說非常笨重或不可能完成的任務。
內聯匯編的用途包括:
使用匯編語言編寫特定的函數;
編寫對速度要求非常較高的代碼;
在設備驅動程序中直接訪問硬體;
編寫 naked 函數的初始化和結束代碼。
二、 關鍵字
使用內聯匯編要用到 __asm 關鍵字,它可以出現在任何允許 C/C++ 語句出現的地方。我們來看一些例子:
簡單的 __asm 塊:
__asm
{
MOV AL, 2
MOV DX, 0xD007
OUT AL, DX
}
在每條匯編指令之前加 __asm 關鍵字:
__asm MOV AL, 2
__asm MOV DX, 0xD007
__asm OUT AL, DX
因為 __asm 關鍵字是語句分隔符,所以可以把多條蠢並腔匯編指令放在同一行:
__asm MOV AL, 2 __asm MOV DX, 0xD007 __asm OUT AL, DX
顯然,第一種方法與 C/C++ 的風格很一致,並且把匯編代碼和 C/C++ 代碼清楚地分開,還避免了重復輸入 __asm 關鍵字,因此推薦使用第一種方法。
不像在 C/C++ 中的"{ }",__asm 塊的"{ }"不會影響 C/C++ 變數的作用范圍。同時,__asm 塊可以嵌套,而且嵌套也不會影響變數的作用范圍。
為了與低版本的 Visual C++ 兼容,_asm 和 __asm 具有相同的意義。另外,Visual C++ 支持標准 C++ 的 asm 關鍵字,但是它不會生成任何指令,它的作用僅限於使編譯器不會出現編譯錯誤。要使用內聯匯編,必須使用 __asm 而不是 asm 關鍵字。
三、 匯編語言
1. 指令集
內聯匯編支持 Intel Pentium 4 和 AMD Athlon 的所有指令。更多其它處理器的指令可以通過 _EMIT 偽指令來創建(_EMIT 偽指令說明見下文)。
2. MASM 表達式
在內聯匯編代碼中,可以使用所有的 MASM 表達式(MASM 表達式是指用來計算一個數值或一個地址的操作符和操作數的組合)。
3. 數據指示符和操作符
雖然 __asm 塊中允許使用 C/C++ 的數據類型和對象,但它不能使用 MASM 指示符和操作符來定義數據對象。這里特別指出,__asm 塊中不允許 MASM 中的定義指示符(DB、DW、DD、DQ、DT 和 DF),也不允許使用 DUP 和 THIS 操作符。MASM 中的結構和記錄也不再有效,內聯匯編不接受 STRUC、RECORD、WIDTH 或者 MASK。
4. EVEN 和 ALIGN 指示符
盡管內聯匯編不支持大多數 MASM 指示符,但它支持 EVEN 和 ALIGN。當需要的時候,這些指示符在匯編代碼裡面加入 NOP 指令(空操作)使標號對齊到特定邊界。這樣可以使某些處理器取指令時具有更高的效率。
5. MASM 宏指示符
內聯匯編不是宏匯編,不能使用 MASM 宏指示符(MACRO、REPT、IRC、IRP 和 ENDM)和宏操作符(>、!、&、% 和 .TYPE)。
6. 段
必須使用寄存器而不是名稱來指明段(段名稱"_TEXT"是無效的)。並且,段跨越必須顯式地說明,如 ES:[EBX]。
7. 類型和變數大小
在內聯匯編中,可以用 LENGTH、SIZE 和 TYPE 來獲取 C/C++ 變數和類型的大大小。
* LENGTH 操作符用來取得 C/C++ 中數組的元素個數(如果不是一個數組,則結果為 1)。
* SIZE 操作符可以獲取 C/C++ 變數的大小(一個變數的大小是 LENGTH 和 TYPE 的乘積)。
* TYPE 操作符可以返回 C/C++ 類型和變數的大小(如果變數是一個數組,它得到的是數組中單個元素的大小)。
例如,程序中定義了一個 8 維的整數型變數:
int iArray[8];
下面是 C 和匯編表達式中得到的 iArray 及其元素的相關值:
__asm C Size
LENGTH iArray sizeof(iArray)/sizeof(iArray[0]) 8
SIZE iArray sizeof(iArray) 32
TYPE iArray sizeof(iArray[0]) 4
8. 注釋
內聯匯編中可以使用匯編語言的注釋,即";"。例如:
__asm MOV EAX, OFFSET pbBuff ; Load address of pbBuff
因為 C/C++ 宏將會展開到一個邏輯行中,為了避免在宏中使用匯編語言注釋帶來的混亂,內聯匯編也允許使用 C/C++ 風格的注釋。
9. _EMIT 偽指令
_EMIT 偽指令相當於 MASM 中的 DB,但是 _EMIT 一次只能在當前代碼段(.text 段)中定義一個位元組。例如:
__asm
{
JMP _CodeLabel
_EMIT 0x00 ; 定義混合在代碼段的數據
_EMIT 0x01
_CodeLabel: ; 這里是代碼
_EMIT 0x90 ; NOP指令
}
10. 寄存器使用
一般來說,不能假定某個寄存器在 __asm 塊開始的時候有已知的值。寄存器的值將不能保證會從 __asm 塊保留到另外一個 __asm 塊中。
如果一個函數聲明為 __fastcall 調用方式,則其參數將通過寄存器而不是堆棧來傳遞。這將會使 __asm 塊產生問題,因為函數無法被告知哪個參數在哪個寄存器中。如果函數接收了 EAX 中的參數並立即儲存一個值到 EAX 中的話,原來的參數將丟失掉。另外,在所有聲明為 __fastcall 的函數中,ECX 寄存器是必須一直保留的。為了避免以上的沖突,包含 __asm 塊的函數不要聲明為 __fastcall 調用方式。
提示:如果使用 EAX、EBX、ECX、EDX、ESI 和 EDI 寄存器,你不需要保存它。但如果你用到了 DS、SS、SP、BP 和標志寄存器,那就應該用 PUSH 保存這些寄存器。
提示:如果程序中改變了用於 STD 和 CLD 的方向標志,必須將其恢復到原來的值。
四、 使用 C/C++ 元素
1. 可用的 C/C++ 元素
C/C++ 與匯編語言可以混合使用,在內聯匯編中可以使用 C/C++ 變數以及很多其它的 C/C++ 元素,包括:
符號,包括標號、變數和函數名;
常量,包括符號常量和枚舉型成員;
宏定義和預處理指示符;
注釋,包括"/**/"和"//";
類型名,包括所有 MASM 中合法的類型;
typedef 名稱,通常使用 PTR 和 TYPE 操作符,或者使用指定的的結構或枚舉成員。
在內聯匯編中,可以使用 C/C++ 或匯編語言的基數計數法。例如,0x100 和 100H 是相等的。
2. 操作符使用
內聯匯編中不能使用諸如"<<"一類的 C/C++ 操作符。但是,C/C++ 和 MASM 共有的操作符(比如"*"和"[]"操作符),都被認為是匯編語言的操作符,是可以使用的。舉個例子:
int iArray[10];
__asm MOV iArray[6], BX ; Store BX at iArray + 6 (Not scaled)
iArray[6] = 0; // Store 0 at iArray+12 (Scaled)
提示:在內聯匯編中,可以使用 TYPE 操作符使其與 C/C++ 一致。比如,下面兩條語句是一樣的:
__asm MOV iArray[6 * TYPE int], 0 ; Store 0 at iArray + 12
iArray[6] = 0; // Store 0 at iArray + 12
3. C/C++ 符號使用
在 __asm 塊中可以引用所有在作用范圍內的 C/C++ 符號,包括變數名稱、函數名稱和標號。但是不能訪問 C++ 類的成員函數。
下面是在內聯匯編中使用 C/C++ 符號的一些限制:
每條匯編語句只能包含一個 C/C++ 符號。在一條匯編指令中,多個符號只能出現在 LENGTH、TYPE 或 SIZE 表達式中。
在 __asm 塊中引用函數必須先聲明。否則,編譯器將不能區別 __asm 塊中的函數名和標號。
在 __asm 塊中不能使用對於 MASM 來說是保留字的 C/C++ 符號(不區分大小寫)。MASM 保留字包含指令名稱(如 PUSH)和寄存器名稱(如 ESI)等。
在 __asm 塊中不能識別結構和聯合標簽。
4. 訪問 C/C++ 中的數據
內聯匯編的一個非常大的方便之處是它可以使用名稱來引用 C/C++ 變數。例如,如果 C/C++ 變數 iVar 在作用范圍內:
__asm MOV EAX, iVar ; Stores the value of iVar in EAX
如果 C/C++ 中的類、結構或者枚舉成員具有唯一的名稱,則在 __asm 塊中可以只通過成員名稱來訪問(省略"."操作符之前的變數名或 typedef 名稱)。然而,如果成員不是唯一的,你必須在"."操作符之前加上變數名或 typedef 名稱。例如,下面的兩個結構都具有 SameName 這個成員變數:
struct FIRST_TYPE
{
char *pszWeasel;
int SameName;
};
struct SECOND_TYPE
{
int iWonton;
long SameName;
};
如果按下面方式聲明變數:
struct FIRST_TYPE ftTest;
struct SECOND_TYPE stTemp;
那麼,所有引用 SameName 成員的地方都必須使用變數名,因為 SameName 不是唯一的。另外,由於上面的 pszWeasel 變數具有唯一的名稱,你可以僅僅使用它的成員名稱來引用它:
__asm
{
MOV EBX, OFFSET ftTest
MOV ECX, [EBX]ftTest.SameName ; 必須使用"ftTest"
MOV ESI, [EBX]. pszWeasel ; 可以省略"ftTest"
}
提示:省略變數名僅僅是為了書寫代碼方便,生成的匯編指令還是一樣的。
5. 用內聯匯編寫函數
如果用內聯匯編寫函數的話,要傳遞參數和返回一個值都是非常容易的。看下面的例子,比較一下用獨立匯編和內聯匯編寫的函數:
; PowerAsm.asm
; Compute the power of an integer
PUBLIC GetPowerAsm
_TEXT SEGMENT WORD PUBLIC 'CODE'
GetPowerAsm PROC
PUSH EBP ; Save EBP
MOV EBP, ESP ; Move ESP into EBP so we can refer
; to arguments on the stack
MOV EAX, [EBP+4] ; Get first argument
MOV ECX, [EBP+6] ; Get second argument
SHL EAX, CL ; EAX = EAX * (2 ^ CL)
POP EBP ; Restore EBP
RET ; Return with sum in EAX
GetPowerAsm ENDP
_TEXT ENDS
END
C/C++ 函數一般用堆棧來傳遞參數,所以上面的函數中需要通過堆棧位置來訪問它的參數(在 MASM 或其它一些匯編工具中,也允許通過名稱來訪問堆棧參數和局部堆棧變數)。
下面的程序是使用內聯匯編寫的:
// PowerC.c
#include
int GetPowerC(int iNum, int iPower);
int main()
{
printf("3 times 2 to the power of 5 is %d\n", GetPowerC( 3, 5));
}
int GetPowerC(int iNum, int iPower)
{
__asm
{
MOV EAX, iNum ; Get first argument
MOV ECX, iPower ; Get second argument
SHL EAX, CL ; EAX = EAX * (2 to the power of CL)
}
// Return with result in EAX
}
使用內聯匯編寫的 GetPowerC 函數可以通過參數名稱來引用它的參數。由於 GetPowerC 函數沒有執行 C 的 return 語句,所以編譯器會給出一個警告信息,我們可以通過 #pragma warning 禁止生成這個警告。
內聯匯編的其中一個用途是編寫 naked 函數的初始化和結束代碼。對於一般的函數,編譯器會自動幫我們生成函數的初始化(構建參數指針和分配局部變數等)和結束代碼(平衡堆棧和返回一個值等)。使用內聯匯編,我們可以自己編寫乾乾凈凈的函數。當然,此時我們必須自己動手做一些有關函數初始化和掃尾的工作。例如:
void __declspec(naked) MyNakedFunction()
{
// Naked functions must provide their own prolog.
__asm
{
PUSH EBP
MOV ESP, EBP
SUB ESP, __LOCAL_SIZE
}
.
.
.
// And we must provide epilog.
__asm
{
POP EBP
RET
}
}
6. 調用 C/C++ 函數
內聯匯編中調用聲明為 __cdecl 方式(默認)的 C/C++ 函數必須由調用者清除參數堆棧,下面是一個調用 C/C++ 函數例子:
#include
char szFormat[] = "%s %s\n";
char szHello[] = "Hello";
char szWorld[] = " world";
void main()
{
__asm
{
MOV EAX, OFFSET szWorld
PUSH EAX
MOV EAX, OFFSET szHello
PUSH EAX
MOV EAX, OFFSET szFormat
PUSH EAX
CALL printf
// 壓入了 3 個參數在堆棧中,調用函數之後要調整堆棧
ADD ESP, 12
}
}
提示:參數是按從右往左的順序壓入堆棧的。
如果調用 __stdcall 方式的函數,則不需要自己清除堆棧。因為這種函數的返回指令是 RET n,會自動清除堆棧。大多數 Windows API 函數均為 __stdcall 調用方式(僅除 wsprintf 等幾個之外),下面是一個調用 MessageBox 函數的例子:
#include
TCHAR g_tszAppName[] = TEXT("API Test");
void main()
{
TCHAR tszHello[] = TEXT("Hello, world!");
__asm
{
PUSH MB_OK OR MB_ICONINFORMATION
PUSH OFFSET g_tszAppName ; 全局變數用 OFFSET
LEA EAX, tszHello ; 局部變數用 LEA
PUSH EAX
PUSH 0
CALL DWORD PTR [MessageBox] ; 注意這里不是 CALL MessageBox,而是調用重定位過的函數地址
}
}
提示:可以不受限制地訪問 C++ 成員變數,但是不能訪問 C++ 的成員函數。
7. 定義 __asm 塊為 C/C++ 宏
使用 C/C++ 宏可以方便地把匯編代碼插入到源代碼中。但是這其中需要額外地注意,因為宏將會擴展到一個邏輯行中。
為了不會出現問題,請按以下規則編寫宏:
使用花括弧把 __asm 塊包圍住;
把 __asm 關鍵字放在每條匯編指令之前;
使用經典 C 風格的注釋("/* comment */"),不要使用匯編風格的注釋("; comment")或單行的 C/C++ 注釋("// comment");
舉個例子,下面定義了一個簡單的宏:
#define PORTIO __asm \
/* Port output */ \
{ \
__asm MOV AL, 2 \
__asm MOV DX, 0xD007 \
__asm OUT DX, AL \
}
乍一看來,後面的三個 __asm 關鍵字好像是多餘的。其實它們是需要的,因為宏將被擴展到一個單行中:
__asm /* Port output */ { __asm MOV AL, 2 __asm MOV DX, 0xD007 __asm OUT DX, AL }
從擴展後的代碼中可以看出,第三個和第四個 __asm 關鍵字是必須的(作為語句分隔符)。在 __asm 塊中,只有 __asm 關鍵字和換行符會被認為是語句分隔符,又因為定義為宏的一個語句塊會被認為是一個邏輯行,所以必須在每條指令之前使用 __asm 關鍵字。
括弧也是需要的,如果省略了它,編譯器將不知道匯編代碼在哪裡結束,__asm 塊後面的 C/C++ 語句看起來會被認為是匯編指令。
同樣是由於宏展開的原因,匯編風格的注釋("; comment")和單行的 C/C++ 注釋("// commen")也可能會出現錯誤。為了避免這些錯誤,在定義 __asm 塊為宏時請使用經典 C 風格的注釋("/* comment */")。
和 C/C++ 宏一樣 __asm 塊寫的宏也可以擁有參數。和 C/C++ 宏不一樣的是,__asm 宏不能返回一個值,因此,不能使用這種宏作為 C/C++ 表達式。
不要不加選擇地調用這種類型的宏。比如,在聲明為 __fastcall 的函數中調用匯編語言宏可能會導致不可預料的結果(請參看前文的說明)。
8. 轉跳
可以在 C/C++ 裡面使用 goto 轉跳到 __asm 塊中的標號處,也可以在 __asm 塊中轉跳到 __asm 塊裡面或外面的標號處。__asm 塊內的標號是不區分大小寫的(指令、指示符等也是不區分大小寫的)。例如:
void MyFunction()
{
goto C_Dest; /* 正確 */
goto c_dest; /* 錯誤 */
goto A_Dest; /* 正確 */
goto a_dest; /* 正確 */
__asm
{
JMP C_Dest ; 正確
JMP c_dest ; 錯誤
JMP A_Dest ; 正確
JMP a_dest ; 正確
a_dest: ; __asm 標號
}
C_Dest: /* C/C++ 標號 */
return;
}
不要使用函數名稱當作標號,否則將轉跳到函數中執行,而不是標號處。例如,由於 exit 是 C/C++ 的函數,下面的轉跳將不會到 exit 標號處:
; 錯誤:使用函數名作為標號
JNE exit
.
.
.
exit:
.
.
.
美元符號"$"用於指定當前指令位置,常用於條件跳轉中,例如:
JNE $+5 ; 下面這條指令的長度是 5 個位元組
JMP _Label
NOP ; $+5,轉跳到了這里
.
.
.
_Label:
.
.
.
五、在 Visual C++ 工程中使用獨立匯編
內聯匯編代碼不易於移植,如果你的程序打算在不同類型的機器(比如 x86 和 Alpha)上運行,你可能需要在不同的模塊中使用特定的機器代碼。這時候你可以使用 MASM(Microsoft Macro Assembler),因為 MASM 支持更多方便的宏指令和數據指示符。
這里簡單介紹一下在 Visual Studio .NET 2003 中調用 MASM 編譯獨立匯編文件的步驟。
在 Visual C++ 工程中,添加按 MASM 的要求編寫的 .asm 文件。在解決方案資源管理器中,右擊這個文件,選擇"屬性"菜單項,在屬性對話框中,點擊"自定義生成步驟",設置如下項目:
命令行:ML.exe /nologo /c /coff "-Fo$(IntDir)\$(InputName).obj" "$(InputPath)"
輸出:$(IntDir)\$(InputName).obj
如果要生成調試信息,可以在命令行中加入"/Zi"參數,還可以根據需要生成 .lst 和 .sbr 文件。
如果要在匯編文件中調用 Windows API,可以從網上下載 MASM32 包(包含了 MASM 匯編工具、非常完整的 Windows API 頭文件/庫文件、實用宏以及大量的 Win32 匯編例子等)。相應地,應該在命令行中加入"/I X:\MASM32\INCLUDE"參數指定 Windows API 匯編頭文件(.inc)的路徑。MASM32 的主頁是:http://www.masm32.com,裡面可以下載最新版本的 MASM32 包。
⑼ 如何C語言與匯編混編
c語言可以嵌套匯編:
按照TC2.0的幫助系統所以說的,在TC2.0下是可以用匯編的,方法是使用asm關鍵字:其格式是:
asm opcode <operands> <;newline>,如同別的注釋一樣,<>之間的表示可選的;例如:
main()
{
char *c="hello,world/n/r$";
asm mov ah,9;asm mov dx,c;asm int 33;
printf("You sucessed!/n");
}
或者是:
main()
{
char *c="hello,world/n/r$";
asm mov ah,9
asm mov dx,c
asm int 33
printf("You sucessed!");
}
兩種格式其實是一種.如果你用的是第一種的樣式,記住:
每一句匯編語句都要以asm開頭,如果一行內有多個句子,
那麼千萬不要忘記在兩個句子之間的這個semicolon(分號),
但是最後一句匯編後面(如果後面沒有其它的語句)的分號可有可無,象第一個例子中的
asm int 33;後面的分號就可以不要,因為它的後面沒有其它
的語句了.但如果是這樣:
asm mov ah,9; asm mov dx,c;asm int 33; printf("You sucessed!");
那麼asm int 33;後面的分號便還是留下好,以免出現編譯錯誤!
在這一點上頗象C語言.
還有一種格式是
asm{ assembly language statement},這種格式應該被普遍的歡迎.
它們的例子如下(其中的語句排列格式與上面兩種相同):
asm{
mov ax,var1
add ax,var2
......
}
但是要注意這種格式TC2.0是不支持的!
只有後來的TC++3.0及後來的IDE支持!
工具的使用:
一旦你的C源文件里包括了這些好東西,則必須用TCC.EXE的COMMAND-LINE來編譯,具體的命令參數TCC.EXE已經提供,這里不復闡述了.最簡單的是:TCC C源文件名(使用這個方法,TCC會自動調用TASM.EXE和TLINK.EXE,並且能夠使TLINK.EXE正確的找到需要的.obj和.lib文件,如果你單步編譯的話,可能會碰到很多的問題,主要是TLINK.EXE它自己並不會去找.obj和.lib文件,你自己可以建一個.bat文件,如果要指定.lib文件的目錄的話可以用/L參數,在文章的後面有一個例子).但大家要注意了,看一下你的TC目錄下面到底是否有TASM.EXE文件,並在TURBOC.CFG(這個文件包括TCC.EXE運行期參數,這裡面所有參數在運很期都將被自動TCC.EXE使用,例如:-IH:/TC/INCLUDE/
-LH:/TC/LIB/)文件中設置好一些參數,並確認TASM.EXE的版本號要2.0以上,以及是否能夠向下兼容.但是在大多數的情況下TC的目錄是沒有TASM.EXE的,或是版本不正常.
如果你有TASM.EXE文件並且TURBOC.CFG文件也已經寫好了,但是還要注意一個
問題:運行TCC.EXE時要在獨立的DOS SHELL下面(不要害怕,這不是一個新東西,我的意思
是,不在諸如TC下的DOS SHELL下面運行,我曾經敗在這個問題下,當我發現時直想揍電腦
一頓,還好沒有,不然就沒有這篇文件了.)
還有一句重要的話:TC2.0支持大部分8086指令(當然用法有一些約定,不過現在我並不打算
進行詳細說明,因為那是一件很繁雜的事,以後有時間或許會寫出來----如果大家需要的話).
如果說上面我所說的那些約定很繁雜的話,那麼下面的方法該是多麼簡單啊!
讓我們使用Borland為TC2.0內建的變數來進行偽匯編.
或許你還不知道在TC2.0中還有一些內建的pseudo寄存器(可以看作是register 型的變數,但是它們比register型的變數好用的多)
_AX,_AH,_AL,
_BX,_BH,_BL,
_CX,_CH,_CL,
_DX,_DH,_DL,
_DI,_SI,_SP,
_CS,_DS,_ES,_SS
注意這些寄存器的size,_AX,_BX,_CX,_DX,_CS,_DS,_ES,_SS,_SI,_DI,_SP等都是16位的寄存器相當於C語言的unsigned int類型,其餘的都是8位的寄存器(相當於unsigned char)(TC怎麼可能支持32位的寄存呢,所以EAX等是不能用的,FS,GS和IP寄存器都是無效的),還有就是在傳遞參數的時候千萬不要忘記使用強制類型轉換.
中斷調用指令是:__int__(interrupt_#)(注意int的前輟和後輟都是兩個underscores)
For example:
#include<dos.h>
unsigned int _stklen=0x200;
unsigned int _heaplen=0;
main()
{
_DX=(unsigned int)"Hello,world./r/n$";
_AX=0x900;
__int__(0x21);
}
dos.h它是包含__int__()內建中斷調用語句的頭文件,因此是不可
缺少的._stklen和_heaplen是定義運行期堆棧和堆大小的兩個內部
引用變數(這是個我自己想的名詞,意指如果這兩個變數在源文件中
顯式的聲明了,那麼編譯程序會自會引用來構造編譯時期的信息以產生
用戶希望的目標文件,如果不顯式的聲明則編譯程序自動確定).
這兩個變數也有一些約定,如果_stklen不顯式聲明,_heaplen賦值為零
都表示棧和堆都是defult的.
最後在TC2.0中還有一個沒有說明的標志位寄存器flags,它也是內建
pseudo寄存器是:_FLAGS,是一個16位寄存器.這些內建的寄存器都可以進行
運算,但是要注意它們所代表的類型(必要時進行類型轉換);
看起來這是不是一種好的辦法啊(而且使用這種方法只要用個一個dos.h頭文件就好,
不需要用TCC編譯,可以直接在TC20的IDE下編譯).
TC2.0中也提供了一些簡單好用的函數來實現對DOS功能的調用如:
int86(...),int86x(...)(但是這些方法實際仍然要調用函數,所以不如使用
偽寄存器,又因為要牽涉到union REGS結構的內存分配所以系統的開銷是增大了,
而使用偽寄存器是最簡潔的),埠通信函數如:inportb(...),inport(...),
outportb(...),outport(...),指針轉換函數:FP_OFF,FP_SEG,MK_FP,這些函數在
幫助系統中都有,有用時大家可以查閱.
tlinkbat.bat的例子:
rem The lib environment variable is the directory of the .obj and .lib file
set lib=h:/tc/lib/
rem 這下面的句子中的c0s(C 零S)是一個.OBJ文件,是一個C程序的STARTUP文件
tlink %lib%c0s %1,%1,%1,/L%lib%emu.lib %lib%maths.lib %lib%cs.lib
set lib=
(使用時可將以rem開頭的句子刪除)
___________________________________________________
一些約定:
我們先說一下在TC20下寫匯編(內聯匯編--自己起的名字,大家可以想叫什麼叫什麼)時的編譯器的編譯原則:
1.所有在main()函數外的的匯編語言的語句都作為數據聲明語句處理,也即在編譯器編譯時會將它放在數據段中,如:
asm string1 db "Hello",,,'world!',0ah,0xd,"$"
main()
{
asm mov dx,offset string1
asm mov ah,9
asm int 33
asm mov dx,offset string2
asm int 33
}
asm string2 db "the string can be declared after the main() function!$"
象這些樣子在main()外面的匯編語言的數據定義語句(事實上不管是什麼匯編語句,
只要是在main()之外,包括這個句子:asm mov ax,0x4c00),在編譯後都放在數據段中,而C語言的數據聲明語句仍按C的規則!
2.所有在main()函內的匯編語言的語句在編譯後都放在代碼段中,包括這個句子:
asm string2 db "the string can be declared after the main() function!$"
3.不要在以asm 開頭的語句中使用C語言的關鍵字,這會導致編譯階段的錯誤
那麼,根據這三條大家會得到什麼樣的結論呢?(先閉上眼想一想,你可能會由此變的
很贊賞自己,是的你應該這樣相信自己是對的!)
讓我們一起看一下這個結論:
1.根據編譯原則1得到:不可以在main()外面寫匯編命令語句(不要笑,正是與C語言相同才值得注意!),在任何地方都不要進行任何的段定義和宏定義(這是因為編譯後的形式決定的,也即:在TC20下所有的匯編格式的語句只能是,直接性的數據定義和語句指令)!
2根據編譯原則2得到:不可以在main()之內使用匯編的語句進行數據定義(同樣不要笑,
大多數人在第一次在TC20下寫匯編都會有這樣的錯誤的)
3.如同類強制類型這樣的事是不可以在以asm開頭的匯編語句中使用的
好了,天即朗,氣瞬清!這樣一說,一個大體的框架就出來了!只要遵守這個原則寫,就可避免很多莫名其妙的錯誤出現!
通俗的說:
匯編語句的數據定義放在main()外面,指令放在main()裡面.
如果你沒有更好的文檔,那麼記住我的這些話!
一些細節的問題:
在以asm開頭的內聯匯編語句中是不支持C的轉義字元的,但是用C語言聲明一個字元數組(含有轉義字元的),然後用int 33 ah=9這功能時輸出這個字元串時,其中的轉義字元是有效的(這主要是因為編譯後其內部表示形式不同造成的,自己想想會有答案的).
內聯匯編支持C的一些如數值表示,字元串聲明格式等,
如:一個十六進制的數據可以用兩種方式表示:0xa 和0ah,字元串可以是這樣:
"Hello,world!$"(如同C)也可以這樣'Hello,world!$'(用匯編自己的方式).
象C一樣你同樣要注意賦值的類型,而且要比C更嚴格(匯編從來不自己動手做
如同類型轉換啊這樣事),所以一切的事完全要你自己做好!而且你不要企圖以C的形式
做這件事,如這樣的格式 asm mov dx,(unsigned)a(a是一個這樣的東西,
char a[ ]="hello,world!";),而且這樣句子也會導致錯誤:asm mov dx,word ptr a(邏輯錯誤),不過這不是在編譯時的錯誤,而是運行期的錯誤(具體的原因自己想一想,象word label這樣的東西的運算作用和會導致的後果),你可以這樣用一個句子做"中間人"如int i=(unsigned)a;asm mov dx,i(也千萬不要用asm mov dx,(unsigned)a 這樣的句子.但是,告訴大家一個好消息,你可以用指針指向一個字元串,然後你會驚訝你竟然可以這樣:
char *p="hello,world";asm mov dx,p,然後用int 33 ah=9的功能輸出這個字元串而不會有錯誤(這也表現出指針的特點,它是一個二位元組的(TC20下)變數,含有的是一個地址,這與其指向的變數的類型是毫無關系的).
內匯匯編語句不支持->這個運算符.還有標號的問題,在最後的例子中你會年看到一些特別之處!
上面所說的只是很細小並微少的一些事(也是很常遇到的),尚有很多的細節要說,但由於本人時間有限不能一一列舉,如C的結構在內聯匯編的應用等大家可以按照其運行機理去想想一下用法;另外,由於這只是一件學習的事,所以還是大家自己學(找一下有關文檔,當然現在已經沒有什麼比較完整的了),情況會好的多,我在對內聯匯編的學習過程中領會到了不少的東西,例如編譯原理方面的知識,以及如何做會使代碼更高效,占空間最少等的方法.最後向大家推薦一種方法,在利用TCC的-S開關可以生成C源文件的匯編代碼
(或許很多的人都用過)是很好的學習材料!祝大家學有所成!
Cstarter
02-11-17
/* 由於個人的時間和能力有限,難免有錯誤和不詳細的地方,請大家見諒!
My Email:[email protected] [email protected] QQ:170594633 */
一些例子:
下面這個例子是對沈美明 溫冬嬋的
<<IBM-PC 匯編語言程序設計>>清華版第十一章程序的改寫
可直接在命令行上鍵入 tcc filename 就可以,當然你要有TASM.EXE
/*
asm mus_frep dw 330,294,262,294,3 p(330)
asm dw 3 p(294),330,392,392
asm dw 330,294,262,294,4 p(330)
asm dw 294,294,330,294,262,-1
asm mus_time dw 6 p(25),50
asm dw 2 p (25,25,50)
asm dw 12 p(25),100
*/
asm mus_frep dw 330,392,330,294,330,392,330,294,330
asm dw 330,392,330,294,262,294,330,392,294
asm dw 262,262,220,196,196,220,262,294,330,262
asm dw -1
asm mus_time dw 3 p (50),25,25,50,25,25,100
asm dw 2 p (50,50,25,25),100
asm dw 3 p (50,25,25),100
main()
{
asm jmp start
/*設置發聲的頻率,這一段在沈美明 溫冬嬋的
<<IBM-PC 匯編語言程序設計>>清華版第十一章有詳細的說明 */
sound:
asm mov al,0b6h
asm out 43h,al
asm mov dx,12h
asm mov ax,533h*896
asm div di
asm out 42h, al
asm mov al,ah
/* 這個延時是用來防止兩次IO操作的最後一次操作的錯誤,
因為CPU比匯流排的速度快很多,所以 要延時等待第一次操作完成後再進行第二次操作*/
asm mov cx,1000
delay:
asm loop delay
asm out 42h,al
asm in al,61h
asm mov ah,al
asm or al,3
asm out 61h,al
/* 使用中斷15H功能86H延時CX:DX=微秒數*/
asm mov ax,2710h
asm mul bx
asm mov cx,dx
asm mov dx,ax
asm mov ah,86h
asm int 15h /*可用__int__(0x15);代替*/
asm mov al,ah
asm out 61h,al
asm jmp add_count
/*------------------*/
start:
asm mov si,offset mus_frep
asm lea bp,mus_time
frep:
asm mov di,[si]
asm cmp di,-1
asm je end_mus
asm mov bx,[bp]
asm jmp sound
add_count: /*標號不能用匯編語言寫*/
asm add si,2
asm add bp,2
asm jmp frep
end_mus:;
}
對於上面的程序大家可用偽寄存器的方法寫一個,要容易的多!
/*一個發聲程序!(引自<<PC技術內幕>>電力版--這個版不好,不如清華版的)*/
#include"dos.h"
main()
{
static union REGS ourregs;
outportb(0x43,0xb6);
outportb(0x42,0xee);
outportb(0x42,0);
outportb(0x61,(inportb(0x61)|0x03));
ourregs.h.ah=0x86;
ourregs.x.cx=0x001e;
ourregs.x.dx=0x8480;
int86(0x15,&ourregs,&ourregs);
outportb(0x61,(inportb(0x61)&0xfc));
}
⑽ 求使用C語言編程調用嵌套匯編語言程序對內存單元進行操作的代碼
內嵌匯編代碼的格式
_asm{
匯編代碼
}