GCC 支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作GCC Inline ASM——GCC内联汇编。这是一个非常有用的功能,有利于我们将一些C/C++语法无法表达的指令直接潜入C/C++代码中,另外也允许我们直接写 C/C++代码中使用汇编编写简洁高效的代码。
1.基本内联汇编
GCC中基本的内联汇编非常易懂,我们先来看两个简单的例子:
__asm__("movl %esp,%eax"); // 看起来很熟悉吧!
或者是
1 2 3 4 5 |
|
或
1 2 3 4 5 |
|
基本内联汇编的格式是
__asm__ __volatile__("Instruction List");
1、__asm__
__asm__是GCC关键字asm的宏定义:
#define __asm__ asm
__asm__或asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。
2、Instruction List
Instruction List是汇编指令序列。它可以是空的,比如:__asm__ __volatile__(""); 或__asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。但并非所有Instruction List为空的内联汇编表达式都是没有意义的,比如:__asm__ ("":::"memory"); 就非常有意义,它向GCC声明:“我对内存作了改动”,GCC在编译的时候,会将此因素考虑进去。
我们看一看下面这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
在 这段代码中,那条内联汇编是被注释掉的。在这条内联汇编之前,内存指针__p所指向的内存被赋值为9999,随即在内联汇编之后,一条if语句判断__p 所指向的内存与9999是否相等。很明显,它们是相等的。GCC在优化编译的时候能够很聪明的发现这一点。我们使用下面的命令行对其进行编译:
$ gcc -O -S example1.c
选项-O表示优化编译,我们还可以指定优化等级,比如-O2表示优化等级为2;选项-S表示将C/C++源文件编译为汇编文件,文件名和C/C++文件一样,只不过扩展名由.c变为.s。
我们来查看一下被放在example1.s中的编译结果,我们这里仅仅列出了使用gcc 2.96在redhat 7.3上编译后的相关函数部分汇编代码。为了保持清晰性,无关的其它代码未被列出。
1 2 3 4 5 6 7 8 9 10 |
|
参 照一下C源码和编译出的汇编代码,我们会发现汇编代码中,没有if语句相关的代码,而是在赋值语句(*__p)=9999后直接return 5;这是因为GCC认为在(*__p)被赋值之后,在if语句之前没有任何改变(*__p)内容的操作,所以那条if语句的判断条件(*__p) == 9999肯定是为true的,所以GCC就不再生成相关代码,而是直接根据为true的条件生成return 5的汇编代码(GCC使用eax作为保存返回值的寄存器)。
我们现在将example1.c中内联汇编的注释去掉,重新编译,然后看一下相关的编译结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
由于内联汇编语句__asm__("":::"memory")向GCC声明,在此内联汇编语句出现的位置内存内容可能了改变,所以GCC在编译时就不能像刚才那样处理。这次,GCC老老实实的将if语句生成了汇编代码。
可能有人会质疑:为什么要使用__asm__("":::"memory")向GCC声明内存发生了变化?明明“Instruction List”是空的,没有任何对内存的操作,这样做只会增加GCC生成汇编代码的数量。
确 实,那条内联汇编语句没有对内存作任何操作,事实上它确实什么都没有做。但影响内存内容的不仅仅是你当前正在运行的程序。比如,如果你现在正在操作的内存 是一块内存映射,映射的内容是外围I/O设备寄存器。那么操作这块内存的就不仅仅是当前的程序,I/O设备也会去操作这块内存。既然两者都会去操作同一块 内存,那么任何一方在任何时候都不能对这块内存的内容想当然。所以当你使用高级语言C/C++写这类程序的时候,你必须让编译器也能够明白这一点,毕竟高 级语言最终要被编译为汇编代码。
你可能已经注意到了,这次输出的汇编结果中,有两个符号:#APP和#NO_APP,GCC将内联汇编语 句中"Instruction List"所列出的指令放在#APP和#NO_APP之间,由于__asm__("":::"memory")中“Instruction List”为空,所以#APP和#NO_APP中间也没有任何内容。但我们以后的例子会更加清楚的表现这一点。
关于为什么内联汇编__asm__("":::"memory")是一条声明内存改变的语句,我们后面会详细讨论。刚才我们花了大量的内容来讨论"Instruction List"为空是的情况,但在实际的编程中,"Instruction List"绝大多数情况下都不是空的。它可以有1条或任意多条汇编指令。