例题 1 一个 C 语言程序及其在 X86/Linux 操作系统上的编译结 果如下 根据所生成的汇编程序来解释程序中四个变 量的存储分配 生存期 作用域和置初值方式等方面 的区别 static long aa = 10; short bb = 20; func( ) { } static long cc = 30; short dd = 40;
static long aa = 10; func( ) { short bb = 20; 例题 1 static long cc = 30; short dd = 40; }.data.align 4.align 4.type cc.2,@object.type aa,@object.size cc.2,4.size aa,4 cc.2: aa:.long 30.long 10.text.globl bb.align 4.align 2.globl func.type bb,@object func:.size bb,2... bb: movw $40,-2(%ebp).value 20...
static long aa = 10; func( ) { short bb = 20; 例题 1 static long cc = 30; short dd = 40; }.data.align 4.align 4.type cc.2,@object.type aa,@object.size cc.2,4.size aa,4 cc.2: aa:.long 30.long 10.text.globl bb.align 4.align 2.globl func.type bb,@object func:.size bb,2... bb: movw $40,-2(%ebp).value 20...
static long aa = 10; func( ) { short bb = 20; 例题 1 static long cc = 30; short dd = 40; }.data.align 4.align 4.type cc.2,@object.type aa,@object.size cc.2,4.size aa,4 cc.2: aa:.long 30.long 10.text.globl bb.align 4.align 2.globl func.type bb,@object func:.size bb,2... bb: movw $40,-2(%ebp).value 20...
func(i) long i; { long j; j= i -1; func(j); } 例题 2
例题 2 func(i) func: long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j esp movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 ebp 变量 j 控制链 call func 函数调用低 addl $4,%esp 恢复栈顶指针返址栈 L1: 参数 i leave 即 mov ebp, esp; pop ebp.... 高 ret 即 pop eip( 下条指令地址 )
例题 2 func(i) func: long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 call func 函数调用 addl $4,%esp 恢复栈顶指针 L1: esp leave 即 mov ebp, esp; pop ebp. ebp... ret 即 pop eip( 下条指令地址 )
例题 2 func(i) func: long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 call func 函数调用 addl $4,%esp 恢复栈顶指针 esp L1: 参数 i leave 即 mov ebp, esp; pop ebp. ebp... ret 即 pop eip( 下条指令地址 )
例题 2 func(i) func: long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 call func 函数调用 esp addl $4,%esp 恢复栈顶指针返址 L1: 参数 i leave 即 mov ebp, esp; pop ebp. ebp... ret 即 pop eip( 下条指令地址 )
例题 2 func(i) func: long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 esp 控制链 call func 函数调用 addl $4,%esp 恢复栈顶指针返址 L1: 参数 i leave 即 mov ebp, esp; pop ebp. ebp... ret 即 pop eip( 下条指令地址 )
例题 2 func(i) func: long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 esp call func 函数调用 ebp 控制链 addl $4,%esp 恢复栈顶指针返址 L1: 参数 i leave 即 mov ebp, esp; pop ebp.... ret 即 pop eip( 下条指令地址 )
例题 2 func(i) func: long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j esp movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 ebp 变量 j 控制链 call func 函数调用低 addl $4,%esp 恢复栈顶指针返址栈 L1: 参数 i leave 即 mov ebp, esp; pop ebp.... 高 ret 即 pop eip( 下条指令地址 )
例题 2 func(i) func: long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j esp movl -4(%ebp),%eax 低 高 ebp 栈 变量 j 控制链 返址 参数 i.... 调用序列之一调用序列之二 pushl %eax 把实参 j 的值压栈 call func 函数调用 addl $4,%esp 恢复栈顶指针 L1: leave 即 mov ebp, esp; pop ebp ret 即 pop eip( 下条指令地址 )
例题 2 func(i) func: long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j esp movl -4(%ebp),%eax 低 高 ebp 栈 变量 j 控制链 返址 参数 i.... 返回序列之一返回序列之二 pushl %eax 把实参 j 的值压栈 call func 函数调用 addl $4,%esp 恢复栈顶指针 L1: leave 即 mov ebp, esp; pop ebp ret 即 pop eip( 下条指令地址 )
例题 2 返回序列之一 func(i) func: 返回序列之二 long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j movl -4(%ebp),%eax esp ebp 控制链 返址 参数 i.... pushl %eax 把实参 j 的值压栈 call func 函数调用 addl $4,%esp 恢复栈顶指针 L1: leave 即 mov ebp, esp; pop ebp ret 即 pop eip( 下条指令地址 )
例题 2 func(i) func: 返回序列之一返回序列之二 long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 call func 函数调用 esp addl $4,%esp 恢复栈顶指针返址 L1: 参数 i leave 即 mov ebp, esp; pop ebp. ebp... ret 即 pop eip( 下条指令地址 )
例题 2 func(i) func: 返回序列之一返回序列之二 long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 call func 函数调用 addl $4,%esp 恢复栈顶指针 esp L1: 参数 i leave 即 mov ebp, esp; pop ebp. ebp... ret 即 pop eip( 下条指令地址 )
例题 2 返回序列之一 func(i) func: 返回序列之二 long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 call func 函数调用 esp ebp.... addl $4,%esp 恢复栈顶指针 L1: leave 即 mov ebp, esp; pop ebp ret 即 pop eip( 下条指令地址 )
例题 2 返回序列之一 func(i) func: 返回序列之二 long i; pushl %ebp 老的基地址指针压栈 { movl %esp,%ebp 修改基地址指针 long j; subl $4,%esp 为 j 分配空间 j= i -1; movl 8(%ebp),%edx 取 i 到寄存器 func(j); decl %edx i 1 } movl %edx,-4(%ebp) i 1 j movl -4(%ebp),%eax pushl %eax 把实参 j 的值压栈 call func 函数调用 esp ebp.... addl $4,%esp 恢复栈顶指针 L1: leave 即 mov ebp, esp; pop ebp ret 即 pop eip( 下条指令地址 )
例题 3 下面的程序运行时输出 3 个整数 试从运行环境和 printf 的实现来分析, 为什么此程序会有 3 个整数输出? main() { printf( %d, %d, %d\n ); }
main() { char *cp1, *cp2; 例题 4 cp1 = "12345"; cp2 = "abcdefghij"; strcpy(cp1,cp2); printf("cp1 = %s\ncp2 = %s\n", cp1, cp2); } 在某些系统上的运行结果是 : cp1 = abcdefghij cp2 = ghij 为什么 cp2 所指的串被修改了?
例题 4 因为常量串 12345 和 abcdefghij 连续分配在常数区执行前 : 1 2 3 4 5 \0 a b c d e f g h i j \0 cp1 cp2
例题 4 因为常量串 12345 和 abcdefghij 连续分配在常数区执行前 : 1 2 3 4 5 \0 a b c d e f g h i j \0 cp1 cp2 执行后 : a b c d e f g h i j \0 f g h i j \0 cp1 cp2
例题 4 因为常量串 12345 和 abcdefghij 连续分配在常数区执行前 : 1 2 3 4 5 \0 a b c d e f g h i j \0 cp1 cp2 执行后 : a b c d e f g h i j \0 f g h i j \0 cp1 cp2 现在的编译器大都把程序中的串常量单独存放在只读数据段中, 因此运行时会报错
例题 5 func(i,j,f,e) short i,j; float f,e; { short i1,j1; float f1,e1; printf(&i,&j,&f,&e); printf(&i1,&j1,&f1,&e1); } main() { short i,j; float f,e; func(i,j,f,e); } Address of i,j,f,e = 36, 42, 44, 54( 八进制数 ) Address of i1,j1,f1,e1 = 26, 24, 20, 14
例题 5 func(i,j,f,e) Sizes of short, int, long, float, short i,j; float f,e; double = 2, 4, 4, 4, 8 { ( 在 SPARC/SUN 工作站上 ) short i1,j1; float f1,e1; printf(&i,&j,&f,&e); printf(&i1,&j1,&f1,&e1); } main() { short i,j; float f,e; func(i,j,f,e); } Address of i,j,f,e = 36, 42, 44, 54( 八进制数 ) Address of i1,j1,f1,e1 = 26, 24, 20, 14
例题 5 func(i,j,f,e) Sizes of short, int, long, float, short i,j; float f,e; double = 2, 4, 4, 4, 8 { ( 在 SPARC/SUN 工作站上 ) short i1,j1; float f1,e1; printf(&i,&j,&f,&e); printf(&i1,&j1,&f1,&e1); } main() 为什么 4 个形式参数 i,j,f,e 的地址 { 间隔和它们类型的大小不一致 short i,j; float f,e; func(i,j,f,e); } Address of i,j,f,e = 36, 42, 44, 54( 八进制数 ) Address of i1,j1,f1,e1 = 26, 24, 20, 14
例题 5 当用传统的参数声明方式时, 编译器不检查实参和形参的个数和类型是否一致, 由程序员自己负责 但对形参和实参是不同的整型, 或不同的实型 - 编译器试图保证运行时能得到正确结果 - 条件是 : 若需数据类型转换时, 不出现溢出 编译器的做法 - 把整型或实型数据分别提升到 long 和 double 类型的数据, 再传递到被调用函数 - 被调用函数根据形参所声明的类型, 决定是否要将传来的实参向低级别类型转换
低地址放高位 例题 5 long short 长整型和短整型 double 高地址放低位 float 双精度型和浮点型
在 main 函数中参数压栈时的观点 例题 5 在 func 函数中存取形式参数时的观点 栈的增长方向 i,4 个字节 j,4 个字节 f,8 个字节 e,8 个字节 2 个字节, 起始地址 36 2 个字节, 起始地址 42 4 个字节, 起始地址 44 4 个字节, 起始地址 54 参数在栈中的情况
例题 6 下面程序为什么死循环 ( 在 SPARC/SUN 工作站上 )? main() { addr(); loop(); } long *p; loop() { long i,j; j=0; for(i=0;i<10;i++){ (*p)--; j++; } } addr() { long k; k=0; p=&k;}
例题 6 将 long *p 改成 short *p,long k 改成 short k 后, 循环体执行一次便停止, 为什么? main() { addr(); loop(); } short *p; loop() { long i,j; j=0; for(i=0;i<10;i++){ (*p)--; j++; } } addr() { short k; k=0; p=&k;}
例题 6 将 long *p 改成 short *p,long k 改成 short k 后, 循环体执行一次便停止, 为什么? main() { addr(); loop(); } short *p; loop() { long i,j; j=0; 低地址放高位 活动记录栈是从高向低方向增长 long i for(i=0;i<10;i++){ (*p)--; j++; } } addr() { short k; k=0; p=&k;} short k 高地址放低位
例题 7 main() { func(); printf("return from func\n"); } func() { char s[4]; strcpy(s,"12345678"); printf("%s\n",s); } 在 X86/Linux 操作系统上的运行结果如下 : 12345678 Return from func Segmentation fault (core dumped)
例题 7 main() { func(); printf("return from func\n"); } func() { char s[4]; strcpy(s,"12345678"); printf("%s\n",s); } esp ebp 低栈高 变量 s 控制链 返址......
例题 7 main() { func(); printf("return from func\n"); } esp func() { char s[4]; ebp strcpy(s,"123456789"); 低 printf("%s\n",s); 栈 } 高 123456789 Segmentation fault (core dumped) 变量 s 控制链 返址......
例题 8 int fact(i) main() int i; { { printf("%d\n", fact(5)); if(i==0) printf("%d\n", fact(5,10,15)); return 1; printf("%d\n", fact(5.0)); else printf("%d\n", fact()); return i*fact(i-1); } } 该程序在 X86/Linux 机器上的运行结果如下 : 120 120 1 Segmentation fault (core dumped)
例题 8 请解释下面问题 : 第二个 fact 调用 : 结果为什么没有受参数过多的影响? 第三个 fact 调用 : 为什么用浮点数 5.0 作为参数时结果变成 1? 第四个 fact 调用 : 为什么没有提供参数时会出现 Segmentation fault?
例题 8 请解释下面问题 : 第二个 fact 调用 : 结果为什么没有受参数过多的影响? 解答 : 参数表达式逆序计算并进栈,fact 能够取到第一个参数
例题 8 请解释下面问题 : 第三个 fact 调用 : 为什么用浮点数 5.0 作为参数时结果变成 1? 解答 : 参数 5.0 转换成双精度数进栈, 占 8 个字节它低地址的 4 个字节看成整数时正好是 0 esp ebp 低栈高 局部变量控制链 返址 参数...
例题 8 请解释下面问题 : 第四个 fact 调用 : 为什么没有提供参数时会出现 Segmentation fault? 解答 : 由于没有提供参数, 而 main 函数又无局部变量, fact 把老 ebp( 控制链 ) (main 的活动记录中保存的 ebp) 当成参数, 它一定是一个很大的整数, 使得活动记录栈溢出 esp ebp 低栈高 局部变量控制链 返址 控制链...