- 论坛徽章:
- 0
|
笔记:
=========
2.8.1 输出操作数是一个earlyclobber操作数
在inline asm中,特别需要注意earlyclobber的情况。 earlyclobber的意思是说,某个输出操作数,可能
在gcc尚未用完所有的输入操作数之前,就已经被写入(inline asm的输出操作)值了。 换言之,gcc尚未使用完
全部的输入操作数的时候,就已经对某些输出操作数产生了输出操作。
earlyclobber是整个gcc inline assembly语法中最晦涩难懂的地方,从而也是程序最容易有BUG的地方。
让我们看一个示例程序:
/* WARNING: WRONG! */
$ cat -n wrong.c
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int var1 = 5, var2 = 5;
6
7 asm volatile
8 (
9 "movl $10, %0\n\t"
10 "movl $20, %1"
11 : "=r" (var1)
12 : "r" (var2)
13 );
14
15 printf("var1 is %d\n", var1);
16 return 0;
17 }
在这个程序中,我们希望给var1变量结合一个寄存器,给var2结合另一个寄存器。 但是程序犯了两个错误:
a) 与var2结合的寄存器是输入操作数,但是第10行给它赋值了;
b) 与var1结合的寄存器是一个earlyclobber,它在gcc尚未访问完毕输入操作数之前就被赋值,但是程序中没有
指出这一点。
错误a)是我们故意犯的,只是为了说明earlyclobber的含义。 错误b)的发生就在于程序没有理解earlyclobber。
我们先看看这个程序的结果是什么:
/* FIXME: 错误程序不一定产生错误结果,有时候需要打开-O2等优化开关才会出错 */
$ gcc -m32 -O2 wrong.c
$ ./a.out
var1 is 20
可以看到,经过inline asm之后,var1的值变成了20,而不是我们期望的10。 为什么会发生这样的结果呢? 我们看看使用
-S和-fverbose-asm编译之后的*.s文件中的相关代码:
movl $5, -12(%ebp) /, var1
movl $15, -8(%ebp) /, var2
movl -8(%ebp), %eax / var2, tmp62
/APP
/ 7 "wrong.c" 1
movl $10, %eax / tmp61
movl $20, %eax / tmp62
/NO_APP
movl %eax, -12(%ebp) / tmp61, var1
从这段汇编代码中可以看出,gcc让var1和var2都结合到了eax寄存器中。 这造成第9行var1被写之后,在第10行中由于var2的
被访问而再次被写——于是得到了错误结果。
正确的程序是:
$ cat correct.c
#include <stdio.h>
int main(void)
{
int var1 = 5, var2 = 15;
asm volatile
(
"movl $10, %0\n\t"
"movl $20, %1"
: "=&r" (var1)
: "r" (var2)
);
printf("var1 is %d\n", var1);
return 0;
}
编译、运行:
$ gcc -m32 correct.c
$ ./a.out
var1 is 10
正是我们期待的结果。 这是因为,输出部的约束部分加上了earlyclobber修饰符:&。 这样,gcc就会保证:
a) 不会把该操作数和任何一个输入操作数结合到一起;
b) 严格检查,该操作数必须和寄存器结合,而 *不是* 任何内存位置
我们看看上面这个正确程序用-S -fverbose-asm编译之后的*.s文件中的相关代码:
movl $5, -12(%ebp) /, var1
movl $15, -8(%ebp) /, var2
movl -8(%ebp), %eax / var2, tmp62
/APP
/ 7 "correct.c" 1
movl $10, %edx /
movl $20, %eax / tmp62
/NO_APP
movl %edx, %eax /, tmp61
movl %eax, -12(%ebp) / tmp61, var1
可以看出,gcc为var1结合了edx,为var2结合了eax。 这正是因为我们声明了var1是一个earlyclobber,从而避免了
gcc的错误行为。 |
|