C语言内嵌汇编代码

嵌入语法

先看一个简单的加法,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int __stdcall add(int a,int b)
{
int c;
__asm(
"MOV ESI, %1;"
"ADD ESI, %2;"
"MOV %0, ESI;"
: "=r"(c)
: "r"(a), "r"(b));
return c;
}
int main()
{
printf("addResult:%d", add(2, 3));
return 0;
}
  1. __stdcall约束了函数传参顺序,保证以从右到左的顺序对参数进行压栈。上例中入栈顺序b,a,最后a在栈顶,b在栈底。

  2. 这里给出在C语言中嵌入汇编代码的一种形式:

    1
    2
    3
    __asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);
    //也可简化为如下形式
    __asm("Instruction List" : Output : Input : Clobber/Modify);

    volatile可选,选用即向GCC编译器声明不对汇编代码进行优化,不选用则GCC会自动决定优化与否;

    “Instruction List”以字符串形式显示指令,一对引号包含一条指令,在引号内加;或\n\t作为结束标志;

    第一个冒号后为输出域,第二个冒号后为输入域。域中格式为”操作符“(变量名),操作符前加修饰符=表示输出,无修饰符表示输入,变量为C代码中变量,多条输入或输出间用逗号隔开;

    从输出域开始,对出现的变量从0开始依次编号,在汇编代码中以寄存器形式进行调用,上例中%0代表c,%1代表a,%2代表b。

    第三个冒号后的参数在这里未用到,作用暂未了解;

操作符(大多数未实测):

操作符 含义
r 通用寄存器$R_0$-$R_{15}$
m 一个有效内存地址
I 数据处理指令中的立即数
X 被修饰的操作符只能作为输出
a, b, c, d 寄存器eax, ebx, ecx,edx
q,r 动态分配的寄存器
g eax,ebx,ecx,edx或内存变量
数字”0” 指定与第0个变量相同的约束

修饰符:

修饰符 说明
被修饰的操作符是只读的
= 被修饰的操作符只写
+ 被修饰的操作符具有可读写的属性
& 被修饰的操作符只能作为输出

执行程序

命令行或git执行下述命令:

1
gcc -fno-builtin -m32 -masm=intel add.c -o add

注:将add.c与add更换为相应的c文件名!

然后打开C文件同一目录下的同名exe文件,也可以:

1
start add.exe

注:将add.exe更换为相应的exe文件名!

  1. -fno-builtin 关闭编译器对汇编代码优化,与前文提到的volatile异曲同工,二者取其一即有效(未实测)。(可选)
  2. -m32 在64位环境下强制进行32位编译。(必选)
  3. -masm=intel 表示使用X86汇编。(X86必选,默认汇编语言不是X86)
  4. 打开exe文件后窗口可能立刻关闭或不便于记录输出结果,可以考虑在代码末端加入getch();或getchar();保持窗口打开,前者读取任意字符后结束进程,后者读取回车后结束进程。

运行效率

随机生成测试数据

使用排序来比较程序执行效率,先随机生成一些待排序数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define cnt 100000
#define delay 5
int main()
{
FILE *in = fopen("sortData.txt", "w");
int i;
for (i = 0; i < cnt / delay;i++){
struct timespec time1 = { 0, 0 };
clock_gettime(CLOCK_REALTIME, &time1);
srand(time1.tv_nsec);
int j = 0;
for(j = 0; j < delay; j++)
{
int srandNum = rand();
printf("%d", srandNum);
fprintf(in, "%d\n", srandNum);
}
}
fclose(in);
return 0;
}

冒泡排序横向对比

分别使用C语言与X86汇编编写冒泡排序函数,进行横向对比。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <stdio.h>
#include<string.h>
#include <time.h>
int nums1[100000 + 5], nums2[100000 + 5];
void bubbleSort(int* arr,int len) {
int temp = 0;
int i, j;
for (i = len - 1; i > 0; i--) {
for (j = 0; j < i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
void __stdcall bubble(int *nums,int count)
{
__asm__ __volatile__(
"MOV ESI, %0;"
"MOV ECX, %1;"
"DEC ECX;"
"outloop:;"
"CMP ECX,1;"
"JL done;"
"MOV EDX,0;"
"MOV EAX,[ESI];"
"innerloop:;"
"CMP EDX,ECX;"
"JGE bottom;"
"INC EDX;"
"MOV EBX,[ESI+EDX*4];"
"MOV EAX,[ESI+EDX*4-4];"
"CMP EBX,EAX;"
"JBE innerloop;"
"swap:;"
"MOV [ESI+EDX*4-4],EBX;"
"MOV [ESI+EDX*4],EAX;"
"JMP innerloop;"
"bottom:;"
"DEC ECX;"
"JMP outloop;"
"done:;" ::"r"(nums),
"r"(count));
}
int main()
{
clock_t st, end;
int len = 0, n;
FILE *in = fopen("sortData.txt", "r");
while(fscanf(in,"%d",n)!=EOF){
nums1[len] = n;
nums2[len] = n;
len++;
}
fclose(in);
st = clock();
bubble(nums1, len);
end = clock();
printf("Intel X86 bubbleSort.asm time: %d\n", end-st);
st = clock();
bubbleSort(nums2, len);
end = clock();
printf("C bubbleSort.c time: %d\n", end-st);
getch();
return 0;
}

某次运行结果:

1
2
Intel X86 bubbleSort.asm time: 6058
C bubbleSort.c time: 18246

多次运行发现在该测试流程下,X86汇编的执行速度始终约为C语言执行速度的三倍。