指针与数组实验
先简单看一下以下c代码
#include <stdio.h>
#include <stdlib.h>
int main() {
char array[10];
array[0] = 0x56;
array[1] = 0x78;
array[9] = 0x12;
char *p = (char *)malloc(10);
p[0] = 0x34;
p[1] = 0x12;
printf("%p\n%p\n%p\n%p\n", array, &array, p, &p);
}
生成目标文件并进行反汇编
gcc point.c -o point -g # 加上调试选项
objdump -S point > point.s -g
gcc point.c -o point -fno-stack-protector -g #关掉栈保护机制
截取主要汇编代码
0000000000001189 <main>:
#include <stdio.h>
#include <stdlib.h>
int main() {
1189: f3 0f 1e fa endbr64
118d: 55 push %rbp
118e: 48 89 e5 mov %rsp,%rbp
1191: 48 83 ec 20 sub $0x20,%rsp
1195: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
119c: 00 00
119e: 48 89 45 f8 mov %rax,-0x8(%rbp)
11a2: 31 c0 xor %eax,%eax
char array[10];
array[0] = 0x56;
11a4: c6 45 ee 56 movb $0x56,-0x12(%rbp)
array[1] = 0x78;
11a8: c6 45 ef 78 movb $0x78,-0x11(%rbp)
array[9] = 0x12;
11ac: c6 45 f7 12 movb $0x12,-0x9(%rbp)
char *p = (char *)malloc(10);
11b0: bf 0a 00 00 00 mov $0xa,%edi
11b5: e8 d6 fe ff ff callq 1090 <malloc@plt>
11ba: 48 89 45 e0 mov %rax,-0x20(%rbp)
p[0] = 0x34;
11be: 48 8b 45 e0 mov -0x20(%rbp),%rax
11c2: c6 00 34 movb $0x34,(%rax)
p[1] = 0x12;
11c5: 48 8b 45 e0 mov -0x20(%rbp),%rax
11c9: 48 83 c0 01 add $0x1,%rax
11cd: c6 00 12 movb $0x12,(%rax)
printf("%p\n%p\n%p\n%p\n", array, &array, p, &p);
11d0: 48 8b 4d e0 mov -0x20(%rbp),%rcx
11d4: 48 8d 75 e0 lea -0x20(%rbp),%rsi
11d8: 48 8d 55 ee lea -0x12(%rbp),%rdx
11dc: 48 8d 45 ee lea -0x12(%rbp),%rax
11e0: 49 89 f0 mov %rsi,%r8
11e3: 48 89 c6 mov %rax,%rsi
11e6: 48 8d 3d 17 0e 00 00 lea 0xe17(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
11ed: b8 00 00 00 00 mov $0x0,%eax
11f2: e8 89 fe ff ff callq 1080 <printf@plt>
11f7: b8 00 00 00 00 mov $0x0,%eax
11fc: 48 8b 7d f8 mov -0x8(%rbp),%rdi
1200: 64 48 33 3c 25 28 00 xor %fs:0x28,%rdi
1207: 00 00
1209: 74 05 je 1210 <main+0x87>
120b: e8 60 fe ff ff callq 1070 <__stack_chk_fail@plt>
1210: c9 leaveq
1211: c3 retq
1212: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
1219: 00 00 00
121c: 0f 1f 40 00 nopl 0x0(%rax)
fs:0x28与linux的堆栈保护机制有关,为简化问题,将该机制关掉。
面试官不讲武德,居然让我讲讲蠕虫和金丝雀!
Why does this memory address %fs:0x28 ( fs[0x28] ) have a random value?
再次反汇编,并加上一些注释
0000000000001169 <main>:
#include <stdio.h>
#include <stdlib.h>
int main() {
1169: f3 0f 1e fa endbr64
116d: 55 push %rbp
116e: 48 89 e5 mov %rsp,%rbp
1171: 48 83 ec 20 sub $0x20,%rsp # 申请32字节堆栈空间
# rbp-1到rbp-10为array空间,array为rbp-10地址别名,array[9]即array+9 * sizeof(char)
# 数组赋值一步到位
char array[10];
array[0] = 0x56;
1175: c6 45 f6 56 movb $0x56,-0xa(%rbp)
array[1] = 0x78;
1179: c6 45 f7 78 movb $0x78,-0x9(%rbp)
array[9] = 0x12;
117d: c6 45 ff 12 movb $0x12,-0x1(%rbp)
char *p = (char *)malloc(10); # 指针变量也是局部变量,占8个字节 rbp-17到rbp-24
1181: bf 0a 00 00 00 mov $0xa,%edi # 申请10个字节空间
1186: e8 e5 fe ff ff callq 1070 <malloc@plt>
118b: 48 89 45 e8 mov %rax,-0x18(%rbp)
# 指针赋值需要两步/三步
p[0] = 0x34;
118f: 48 8b 45 e8 mov -0x18(%rbp),%rax # 取得指针变量中存储的地址
1193: c6 00 34 movb $0x34,(%rax) # 将值写入该地址
p[1] = 0x12;
1196: 48 8b 45 e8 mov -0x18(%rbp),%rax # 取得指针变量中存储的地址
119a: 48 83 c0 01 add $0x1,%rax # 设置偏移量
119e: c6 00 12 movb $0x12,(%rax) # 将值写入该地址
# lea: Load Effective Address,即装入有效地址的意思,它的操作数就是地址
printf("%p\n%p\n%p\n%p\n", array, &array, p, &p);
11a1: 48 8b 4d e8 mov -0x18(%rbp),%rcx # p
11a5: 48 8d 75 e8 lea -0x18(%rbp),%rsi # &p
11a9: 48 8d 55 f6 lea -0xa(%rbp),%rdx # &array
11ad: 48 8d 45 f6 lea -0xa(%rbp),%rax # array
11b1: 49 89 f0 mov %rsi,%r8
11b4: 48 89 c6 mov %rax,%rsi
11b7: 48 8d 3d 46 0e 00 00 lea 0xe46(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
11be: b8 00 00 00 00 mov $0x0,%eax
11c3: e8 98 fe ff ff callq 1060 <printf@plt>
11c8: b8 00 00 00 00 mov $0x0,%eax
11cd: c9 leaveq
11ce: c3 retq
11cf: 90 nop
C++内存模型以及寄存器指针rsp和rbp
看完汇编代码,可以很容易猜到:p与&p结果不一样,array与&array结果一致。
0x7ffd85499f96
0x7ffd85499f96
0x55e303c302a0
0x7ffd85499f88
使用gdb显示相关数据
可以使用examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示:
x/<n/f/u> <addr>
n:是正整数,表示需要显示的内存单元的个数,即从当前地址向后显示n个内存单元的内容,
一个内存单元的大小由第三个参数u定义。
f:表示addr指向的内存内容的输出格式,s对应输出字符串,此处需特别注意输出整型数据的格式:
x 按十六进制格式显示变量.
d 按十进制格式显示变量。
u 按十进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。
u:就是指以多少个字节作为一个内存单元-unit,默认为4。u还可以用被一些字符表示:
如b=1 byte, h=2 bytes,w=4 bytes,g=8 bytes.
<addr>:表示内存地址。
Reading symbols from ./point...
(gdb) b point.c:11
Breakpoint 1 at 0x11a1: file point.c, line 11.
(gdb) r
Starting program: /root/tianchi/codebase/expr/point/point
Breakpoint 1, main () at point.c:11
11 printf("%p\n%p\n%p\n%p\n", array, &array, p, &p);
(gdb) p &array
$1 = (char (*)[10]) 0x7fffffffdef6
(gdb) p &p
$2 = (char **) 0x7fffffffdee8
(gdb) p p
$3 = 0x5555555592a0 "4\022"
(gdb) x/10ab 0x5555555592a0 # 查看指针变量指向区域内容
0x5555555592a0: 0x34 0x12 0x0 0x0 0x0 0x0 0x0 0x0
0x5555555592a8: 0x0 0x0
(gdb) x/32tb 0x7fffffffdee8 # 打印该地址之上32字节的内容,二进制
0x7fffffffdee8: 10100000 10010010 01010101 01010101 01010101 01010101 00000000 00000000
0x7fffffffdef0: 11110000 11011111 11111111 11111111 11111111 01111111 01010110 01111000
0x7fffffffdef8: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00010010
0x7fffffffdf00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
(gdb) x/32ab 0x7fffffffdee8 # 打印该地址之上32字节的内容,十六进制,有些前面有fff是符号位补全的原因(符号位为1),小端方式,低位在低地址
0x7fffffffdee8: 0xffffffffffffffa0 0xffffffffffffff92 0x55 0x55 0x55 0x55 0x0 0x0
0x7fffffffdef0: 0xfffffffffffffff0 0xffffffffffffffdf 0xffffffffffffffff 0xffffffffffffffff 0xffffffffffffffff 0x7f 0x56 0x78
0x7fffffffdef8: 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x12
0x7fffffffdf00: 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
(gdb) x/1tg 0x7fffffffdee8 # 打印指针变量内容 二进制
0x7fffffffdee8: 0000000000000000010101010101010101010101010101011001001010100000
(gdb) x/1ag 0x7fffffffdee8 # 打印指针变量内容 十六进制
0x7fffffffdee8: 0x5555555592a0
(gdb)
注意区分“地址y”和“地址y的内容”之间的区别
指针 | 数组 |
---|---|
保存数据的地址 | 保存数据 |
间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据。 如果指针有一个下标[i],就把指针的内容加上i为地址,从中提取数据 | 直接访问数据,a[i]只是简单地以a+i为地址取得数据 |
通常用于动态结果 | 通常用于存储固定数目且数据类型相同的元素 |
相关的函数为malloc,free | 隐式分配和删除 |
通常指向匿名数据 | 自身即为变量名 |