|
下面代码来自 APUE2 (运行环境 linux 2.6.x):
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
static void f1(int, int, int, int);
static void f2(void);
static jmp_buf jmpbuffer;
static int globval;
int main(void)
{
int autoval;
register int regival;
volatile int volaval;
static int statval;
globval = 1; autoval = 2; regival = 3; volaval = 4; statval = 5;
if (setjmp(jmpbuffer) != 0) {
printf ("after longjmp: \n");
printf ("globval = %d, autoval = %d, regival = %d, volaval = %d, statval = %d\n",
globval, autoval, regival, volaval, statval);
exit (0);
}
globval = 95; autoval = 96; regival = 97; volaval = 98; statval = 99;
f1(autoval, regival, volaval, statval);
exit (0);
}
static void f1(int i, int j, int k, int l)
{
printf ("in f1():\n");
printf ("globval = %d, autoval = %d, regival = %d, volaval = %d, statval = %d\n", globval, i, j, k, l);
f2();
}
static void f2(void)
{
longjmp(jmpbuffer, 1);
}
编译程序时不进行任何优化:[root@centos C]# gcc setlongjmp.c -o setlongjmp
[root@centos C]# ./setlongjmp
in f1():
globval = 95, autuval = 96, regival = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99 带优化的编译:[root@centos C]# ./setlongjmp
in f1():
globval = 95, autuval = 96, regival = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 2, regival = 3, volaval = 98, statval = 99 从经过优化后的代码输出可见,autoval 和 regival 这两个变量(前者是自动变量,后者是寄存器变量)的的值回滚为 main 中的初值 2 和 3 。
不带优化的代码两次的输出的结果都一样,其原因是因为不优化,所以不追求执行的效率和速度,因此各个变量的读取和写入都是老老实实的通过操作内存中的栈来进行的。
那么通过优化后,到底发生了些什么情况呢?
通过 objdump 观察可执行文件或者是使用 gdb 跟踪可执行文件,可以看到,对于自动变量 autoval 和 寄存器变量 regival 是不经过内存操作的: 8048476: c7 05 3c 99 04 08 01 movl $0x1,0x804993c
804847d: 00 00 00
8048480: c7 45 f4 04 00 00 00 movl $0x4,0xfffffff4(%ebp)
8048487: c7 05 80 98 04 08 05 movl $0x5,0x8049880 上面这段汇编对应于 main 中的 globval = 1; autoval = 2; regival = 3; volaval = 4; statval = 5; 这几条语句,从中看出并没有给 autoval 和 regival 进行赋值的语句。
而且还有对于 globval = 95; autoval = 96; regival = 97; volaval = 98; statval = 99; 这几条赋值语句时: 80484ef: c7 05 3c 99 04 08 5f movl $0x5f,0x804993c
80484f6: 00 00 00
80484f9: c7 45 f4 62 00 00 00 movl $0x62,0xfffffff4(%ebp)
8048500: c7 05 80 98 04 08 63 movl $0x63,0x8049880 仍没有对 autoval 和 regival 的复制,并且即使在即将进入 f1() 时对函数参数压栈的动作中还是没看到对 autoval 和 regival 的压栈。然后在执行 f1() 中的第 2 个 printf() 时才会看到 autoval 和 regvival 这两个变量值的入栈:8048519: c7 44 24 14 63 00 00 movl $0x63,0x14(%esp)
8048520: 00
8048521: 89 5c 24 10 mov %ebx,0x10(%esp)
8048525: c7 44 24 0c 61 00 00 movl $0x61,0xc(%esp)
804852c: 00
804852d: c7 44 24 08 60 00 00 movl $0x60,0x8(%esp)
8048534: 00
8048535: a1 3c 99 04 08 mov 0x804993c,%eax
804853a: 89 44 24 04 mov %eax,0x4(%esp)
804853e: c7 04 24 94 86 04 08 movl $0x8048694,(%esp)
8048545: e8 0e fe ff ff call 8048358 <printf@plt> 由上可见,对于 autoval 和 regival 的操作是利用立即数来操作的,也就相当于说当编译器在编译完可执行文件后,autoval 和 regival 两个值就已经以立即数存在于可执行文件当中。所以在进入 f1() 中时,也没有必要像未经优化那样老老实实的按照参数入栈顺序入栈。
从 longjmp 返回到 setjmp 底下也能观察到这样的现象:80484b6: 89 54 24 14 mov %edx,0x14(%esp)
80484ba: 89 44 24 10 mov %eax,0x10(%esp)
80484be: c7 44 24 0c 03 00 00 movl $0x3,0xc(%esp) 80484c5: 00
80484c6: c7 44 24 08 02 00 00 movl $0x2,0x8(%esp) 80484cd: 00
80484ce: a1 3c 99 04 08 mov 0x804993c,%eax
80484d3: 89 44 24 04 mov %eax,0x4(%esp)
80484d7: c7 04 24 4c 86 04 08 movl $0x804864c,(%esp)
80484de: e8 75 fe ff ff call 8048358 <printf@plt> 从上面分析看到,优化手段之一就是对自动变量和寄存器变量用“立即数”来代替!
需要注意的是,全局变量,静态变量以及易失变量它们是不受优化影响的。全局变量有全局变量的存储空间,静态变量有静态变量的存储空间,易失变量它虽然没有自己特定的空间,但它不接受优化,所以它不会像上面那样和自动变量,寄存器变量被优化成“立即数”,它仍然是老老实实的入栈出栈。
所以,当我们编写一个使用非局部跳转(使用 longjmp 的跳转)的可移植程序时,必须考虑使用 volatile 属性,否贼从一个系统移植到另一个系统时,不能保证你还是原来的你,甚至是面目全非。 |
|