《C语言编程》__asm__ volatile 内联汇编

__asm__ volatile 之 C语言嵌入式汇编 该文章详细介绍了,但不限于:

  1. __asm__ 、volatile的原理和作用
  2. 内联函数的格式

什么是内联汇编?

GCC 支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作GCC Inline ASM——GCC内联汇编。这是一个非常有用的功能,有利于我们将一些C/C++语法无法表达的指令直接潜入C/C++代码中,另外也允许我们直接写 C/C++代码中使用汇编编写简洁高效的代码。

其实就是在C/C++中加入汇编语句,一个是有一些操作无法使用高级语言,只能用汇编;另一个是高级语言虽然易读,但是执行效率肯定没有汇编高。

基本内联汇编

__asm__("movl %esp,%eax"); // 括号里一定要加引号,引号内是汇编语句

__asm__是GCC关键字asm的宏定义:

#define __asm__ asm

__asm__或asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。

选项-O表示优化编译,我们还可以指定优化等级,比如-O2表示优化等级为2;选项-S表示将C/C++源文件编译为汇编文件,文件名和C/C++文件一样,只不过扩展名由.c变为.s。

内联汇编的意义

Instruction List是汇编指令序列(实际就是括号里的东西)。它可以是空的,比如:__asm__ __volatile__(""); 或__asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。但并非所有Instruction List为空的内联汇编表达式都是没有意义的,

__asm__ ("":::"memory");

就非常有意义,它向GCC声明:“我对内存作了改动”,GCC在编译的时候,会将此因素考虑进去。

为什么要声明对内存作了改动?文章中有细致的证明和介绍,我也看不懂汇编。总的来说,就是编译器会优化编译,为了防止编译优化。比如说

*p = 999;
if (*p == 999){
	return 0;
} else {
	return 1;
}

这么一段代码,反汇编后,没有进行if 判断,在对p指针所指向的内存进行赋值999后,直接return 0;编译器很聪明啊,它知道在复制后,没有对该地址的内存进行修改,肯定是999了,这还用想?

  1. 大多数情况来看,如果这个p指针是局部变量,没人会访问到,那没问题。
  2. 但如果是全局变量,大家都访问的到的,其他线程访问的到, 就会造成刚赋值p,其他线程也立即修改为888, 这个时候*p=888。所以这也是为什么多线程操作全局变量要加锁,或者进行原子访问的原因。那么问题来了,这个时候是返回0还是1?

我个人理解还是会返回0,因为编译器并不会替你考虑到,全局未加锁的问题,他还是认为这个值改完没人改,这块内存没人动, 还是会优化掉if。

(我个人也没想到写过向一个变量赋一个确定的值,又去判断的代码。对可能会多线程同时操作一个变量的情况,也采用了加锁或原子操作)

  1. 还有一种情况,如果你现在正在操作的内存是一块内存映射,映射的内容是外围I/O设备寄存器。那么操作这块内存的就不仅仅是当前的程序,I/O设备也会去操作这块内存。既然两者都会去操作同一块内存,那就不能认为自己是对该内存的独占。

那么这种情况也没法对IO设备加锁,只好通过协议来协商,我要开始写入了,我写完了,你读吧。

总结:

所以当你使用高级语言C/C++写这类程序的时候,你必须让编译器也能够明白这一点,毕竟高级语言最终要被编译为汇编代码。

__asm__ ("":::"memory");就是告诉编译器,你可别觉得我这块内存是不可变的,别整那些编译优化了。

内联汇编的格式

当在"Instruction List"中有多条指令的时候,你可以在一对引号中列出全部指令,也可以将一条或几条指令放在一对引号中,所有指令放在多对引号中。如果是前者,你可以将每一条指令放在一行,如果要将多条指令放在一行,则必须用分号(;)或换行符(/n,大多数情况下/n后还要跟一个/t,其中/n是为了换行,/t是为了空出一个tab宽度的空格)将它们分开。比如:

__asm__("movl %eax, %ebx sti popl %edi subl %ecx, %ebx");
__asm__("movl %eax, %ebx; sti popl %edi; subl %ecx, %ebx");
__asm__("movl %eax, %ebx; sti/n/t popl %edisubl %ecx, %ebx");

都是合法的。

如果你将指令放在多对引号中,则除了最后一对引号之外,前面的所有引号里的最后一条指令之后都要有一个分号(;)或(/n)或(/n/t)。比如:

__asm__("movl %eax, %ebx sti/n" "popl %edi;" "subl %ecx, %ebx");
__asm__("movl %eax, %ebx; sti/n/t" "popl %edi; subl %ecx, %ebx");
__asm__("movl %eax, %ebx; sti/n/t popl %edi/n""subl %ecx, %ebx");
__asm__("movl %eax, %ebx; sti/n/t popl %edi;" "subl %ecx, %ebx");

都是合法的。

上述原则可以归结为:

  • 任意两个指令间要么被分号(;)分开,要么被放在两行;
  • 放在两行的方法既可以从通过/n的方法来实现,也可以真正的放在两行;
  • 可以使用1对或多对引号,每1对引号里可以放任一多条指令,所有的指令都要被放到引号中。

在基本内联汇编中,“Instruction List”的书写的格式和你直接在汇编文件中写非内联汇编没有什么不同,你可以在其中定义Label,定义对齐(.align n ),定义段(.section name )。Linux内联代码常用的格式,非常整齐。也建议大家都使用这种格式来写内联汇编代码,例如:

__asm__(".align 2/n/t" "movl %eax, %ebx/n/t" "test %ebx, %ecx/n/t" "jne error/n/t" "sti/n/t" "error: popl %edi/n/t" "subl %ecx, %ebx");

__volatile__是GCC关键字volatile的宏定义:

#define __volatile__ volatile

__volatile__ 或volatile是可选的,你可以用它也可以不用它。如果你用了它,则是向GCC声明“不要动我所写的Instruction List,我需要原封不动的保留每一条指令”,否则当你使用了优化选项(-O)进行编译时,GCC将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。

接着阅读文章简直是一种折磨,文章很好,是自己的造诣还没到。用到的时候再研究吧,能够在看到代码的时候知道这块大概是啥意思就达到我的目标了。

  • 13
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值