个人认为SSD6 Exercise 1是卡内基有史以来最经典的题目,本来想写一篇大家都看得懂的长篇大论,无奈时间不够,只好延续以往记流水帐的风格,把心得一条一条列出来。
心得1.从汇编的层次去理解c语言传值和传地址的区别.看实例
把这段代码对应的汇编为:
调用process_keys34之前和之后的栈帧结构如下:
调用前main函数的栈帧: 调用后main函数和process_keys34函数的栈帧:
%ebp <-----帧指针%ebp %ebp
....... .......
start start
stride stride
key3 key3
key4 key4
msg1 msg1
....... <-----栈指针%esp ........
&key3 <--------实参&key3
&key4 <--------实参&key4
返回地址
%ebp <-----帧指针%ebp
局部变量
...... <-----栈指针%esp
返回地址是main函数调用process_keys34后继续执行地方,如果我们想让程序执行完process_keys34后跳到其他地方,就可以修改这个返回地址,要想修改返回地址的值,必须先得到存储返回地址的那部分内存地址,在gdb里面可以通过$ebp+4得到,而在程序里面只能通过返回地址上面那个变量得到,即&key4对应的地址减1.理解了这点是解题的关键。
同理调用extrat_message之前和之后的栈帧结构如下:
调用前main函数的栈帧: 调用后main函数和extrat_message函数的栈帧:
%ebp <-----帧指针%ebp %ebp
....... .......
start start
stride stride
key3 key3
key4 key4
msg1 msg1
....... <-----栈指针%esp ........
stride的副本 <--------实参stride
start的副本 <--------实参start
返回地址
%ebp <-----帧指针%ebp
局部变量
...... <-----栈指针%esp
心得2:指针符*和地址符&的混合计算
1. (char *) data + j 与 data + j
data是一个int型的数组,data表示数组的首地址,我们知道地址其实是一个整数,任何地址都一样,一个指向int的地址和一个指向char的地址本质上是一样的,都是整数。但,c语言的编译器会区别这两种指针,如果指正ptr指向int,那么 ptr + 1 的地址为 <ptr + 4>:ptr的地址加上4个字节,如果ptr指向char,那么 ptr + 1 的地址为 <ptr + 1>:ptr的地址加上1个字节。当执行指针的加减法运算时,c语言会根据指针所指向的变量类型来确定需要加减多少地址。这里先把data强制转换成char类型的指针,那么
(char *) data + j 的真实地址是: <data + j>
data + j 的真实地址是: <data + 4*j>
2. *( (int *)&key3 + 1 ) + 1 与 *(&key3 + 1) + 1
其中 int a = 4; int * key3 = &a;所以 key3是一个指向int型的指针,也可以说key3的变量类型为(int *),那么&key3就是指向指针的指针,变量类型为(int **),比key3多了一个星号,但两者本质是一样的,都是地址,都是一个整数,都占4个字节,且加1后都会在原来的地址上再加4个字节,唯一的却别就是这两个地址存放变量的类型不同,*( (int *)&key3 + 1 )是一个int型的值,而
*(&key3 + 1)是一个(int *)的值,两者分别加1运算后,前者加1,后者加4,这就是区别。
3.指针和地址的区别
指针是变量,地址是值,指针的值本质上是一个地址
心得1.从汇编的层次去理解c语言传值和传地址的区别.看实例
- intmain(intargc,char*argv[]){
- intstart=10;
- intstride=3;
- intkey3=-1;
- intkey4=777;
- char*msg1;
- process_key34(&key3,&key4); //传地址
- msg1=extract_message(start,stride); //传引用
- return0;
- }
- process_keys34(&key3,&key4);
- lea-0x28(%ebp),%eax取key3的地址,即&key3
- mov%eax,0x4(%esp)把&key3存到%esp+4
- lea-0x24(%ebp),%eax取key4的地址,即&key4
- mov%eax,(%esp)把&key4存到%esp
- call804858d调用函数process_keys34
- msg1=extract_message1(start,stride);
- mov-0x10(%ebp),%eax取实参start的值
- mov%eax,0x4(%esp)得到start的副本,存到%esp+4
- mov-0x14(%ebp),%eax取实参stride的值
- mov%eax,(%esp)得到stride的副本,存到%esp
- call80485ba调用extract_message1方法
- mov%eax,-0xc(%ebp)把返回值赋给msg1
调用前main函数的栈帧: 调用后main函数和process_keys34函数的栈帧:
%ebp <-----帧指针%ebp %ebp
....... .......
start start
stride stride
key3 key3
key4 key4
msg1 msg1
....... <-----栈指针%esp ........
&key3 <--------实参&key3
&key4 <--------实参&key4
返回地址
%ebp <-----帧指针%ebp
局部变量
...... <-----栈指针%esp
返回地址是main函数调用process_keys34后继续执行地方,如果我们想让程序执行完process_keys34后跳到其他地方,就可以修改这个返回地址,要想修改返回地址的值,必须先得到存储返回地址的那部分内存地址,在gdb里面可以通过$ebp+4得到,而在程序里面只能通过返回地址上面那个变量得到,即&key4对应的地址减1.理解了这点是解题的关键。
同理调用extrat_message之前和之后的栈帧结构如下:
调用前main函数的栈帧: 调用后main函数和extrat_message函数的栈帧:
%ebp <-----帧指针%ebp %ebp
....... .......
start start
stride stride
key3 key3
key4 key4
msg1 msg1
....... <-----栈指针%esp ........
stride的副本 <--------实参stride
start的副本 <--------实参start
返回地址
%ebp <-----帧指针%ebp
局部变量
...... <-----栈指针%esp
心得2:指针符*和地址符&的混合计算
1. (char *) data + j 与 data + j
data是一个int型的数组,data表示数组的首地址,我们知道地址其实是一个整数,任何地址都一样,一个指向int的地址和一个指向char的地址本质上是一样的,都是整数。但,c语言的编译器会区别这两种指针,如果指正ptr指向int,那么 ptr + 1 的地址为 <ptr + 4>:ptr的地址加上4个字节,如果ptr指向char,那么 ptr + 1 的地址为 <ptr + 1>:ptr的地址加上1个字节。当执行指针的加减法运算时,c语言会根据指针所指向的变量类型来确定需要加减多少地址。这里先把data强制转换成char类型的指针,那么
(char *) data + j 的真实地址是: <data + j>
data + j 的真实地址是: <data + 4*j>
2. *( (int *)&key3 + 1 ) + 1 与 *(&key3 + 1) + 1
其中 int a = 4; int * key3 = &a;所以 key3是一个指向int型的指针,也可以说key3的变量类型为(int *),那么&key3就是指向指针的指针,变量类型为(int **),比key3多了一个星号,但两者本质是一样的,都是地址,都是一个整数,都占4个字节,且加1后都会在原来的地址上再加4个字节,唯一的却别就是这两个地址存放变量的类型不同,*( (int *)&key3 + 1 )是一个int型的值,而
*(&key3 + 1)是一个(int *)的值,两者分别加1运算后,前者加1,后者加4,这就是区别。
3.指针和地址的区别
指针是变量,地址是值,指针的值本质上是一个地址