GNU C 内联汇编介绍

1、很早之前就听说 C 语言能够直接内嵌汇编指令。但是之前始终没有去详细了解过。最近由于某种需求,看到了相关的 C 语言代码。也就自然去简单的学习了一下如何在 C 代码中内嵌汇编指令。

asm/__asm__ 关键字

1、总的来说在 C 代码中我们通过 asm/__asm__ 关键字来告诉编译器将指定的内容当汇编指令处理。废话不多说,先看个例子:

#include <stdio.h> int main(int argc, char *argv[]) { int x = 3, y = 4; __asm__("addl %%ebx, %%eax" : "=a" (y) : "b" (x), "a" (y)); printf("x + y = %d\n", y); return 0; }

2、这个例子,求两数之和。将 x 的值加到 y 中,并输出 y 值。首先来看一下在 C 代码中插入汇编指令的框架代码:

__asm__("汇编指令1\n\t" "汇编指令2\n\t" "汇编指令3\n\t" "汇编指令n" : 输出变量列表 : 输入变量列表 : 被破坏的寄存器列表); 汇编指令

1、在 __asm__(); 的“”中,便是编写汇编指令的地方。利用 C 语言自动连接双引号的特性,我们可以像框架那样每一行只写一条指令,当然你也可以全部写在一行,那么需要用 ';' 将不同的指令分开。

2、\n 用于指令换行,\t使 GCC 编译的时候产生的汇编指令格式保持规范。
GCC 默认使用 AT&T 格式的汇编语法 它与 intel 的汇编语法之间稍有不同。简单说两点不同的地方:

AT&T 汇编在操作寄存器时需要在前面加一个 '%' 符号,而 intel 的不用。由于在 C 代码中嵌入汇编时,写在字符串中,由于 '%' 在 C 语言中是特殊字符,所以为什么在第一个例子中寄存器前加了两个 '%'.

AT&T 在操作立即数时,需要在立即数前面加 '$',而 intel 却是 '#'.

AT&T 的源与目的与 intel 相反。例如: intel:mov eax, #1 AT&T:movl $1, %eax.

3、这里只是提到了本文中会见到的一部分差异,更多具体关于 AT&T 汇编的知识,这里就不再赘述。可参见相关描述 AT&T 汇编的书籍。

输出变量列表

1、输出变量列表是描述,在内嵌的汇编指令中将哪些值输出到 C 代码环境中的哪个变量中。比如第一个例子中我们指定在执行完了所写的汇编指令后将 eax 寄存器的值输出到变量 y 中。
其中 "=a" 指明使用 eax 寄存器为输出寄存器,输出到紧跟的变量 (y) 中。

= 代表输出变量用作输出,原来的值会被新值替换。

+ 代表即可用作输入,也可用作输出。

2、输出变量列表可以写多个变量,每个之间使用逗号隔开。例如:: “=a” (x), "=b" (y), "=r" (z)。其中用到的 a, b 等代表相应的寄存器。如下是一部分对应关系。

代码含义
a   使用寄存器 eax  
b   使用寄存器 ebx  
c   使用寄存器 ecx  
d   使用寄存器 edx  
S   使用 esi  
D   使用 edi  
q   使用动态分配字节可寻址寄存器  
r   使用任意动态分配的寄存器  
A   使用寄存器 eax 与 edx 联合  
m   使用内存地址  
o   使用内存地址并可以加偏移量  
I   使用常数 0-31  
J   使用常数 0-63  
K   使用常数 0-255  
M   使用常数 0-3  
N   使用一字节常数 0-255  

3、这里仅仅列出了一部分常用到的代码,更多详细请参考 GNU C 的 GCC 使用手册。
这里讲一下 "=r" 的用法,像 a, b 这些代码都是指定使用的寄存器。但是 r 是让编译器随机给一个,那么我怎么知道是那个呢?
不用担心,编译器为使用的随机寄存器遍了一个号。规则是:从输出列表开始,一直到输入列表结束,从左到右,从上到下一次为 %0, %1, %2....所以我们可以这样改写第一个代码例子:

#include <stdio.h> int main(int argc, char *argv[]) { int x = 3, y = 4; __asm__("addl %1, %0" : "=r" (y) : "r" (x), "0" (y)); printf("x + y = %d\n", y); return 0; } 输入变量列表

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/438ab95f688f45a15cf86f0d4966477d.html