碰到这样一个问题,代码简化之后如下:
1 2 3 4 5 6 7 8 9
| #include <stdio.h> char *get_string(){ char *string = "hello world"; return string; } int main(int argc, char *argv[]){ char *result = get_string(); printf("%s\n",result); }
|
代码很简单,在 main 中调用 get_string 函数,这个函数返回一个指向”hello world”字符串的指针,打印出这个字符串,能输出helloworld
。
这里就有一个问题,推理如下:
- 每一个函数调用,就会为这个函数分配栈内存,所谓栈帧,这块内存用于存放函数内的局部变量、实参等,函数返回这块内存是会就会被回收。
- get_string函数调用时,就会在其栈帧中分配一块内存用于存放”hello world”,然后把这块内存的首地址赋值给string.
- get_string 函数返回string, 实际上就是将存放”helloworld”的这块内存地址返回赋值给main中的result变量。
- 这时虽然result指向原先”hello world”的内存地址,但get_string的栈帧已经被回收了,helloworld都不存在了,怎么还能打印出helloworld呢?
真是百思不得其解,为了验证上面的推理没有错,稍微改动一下代码,做一下实验:
1 2 3 4 5 6 7 8 9
| int *get_int(){ int a = 100; return &a; }
int main(int argc, char *argv[]){ int *result = get_int(); printf("%d\n",*result); }
|
这里我把char换成了int来实验。声明了一个int a = 100;然会返回a的地址。果不其然编译时就收获了一个warning,警告我们返回了一个函数内局部变量的地址。
1 2
| warning: function returns address of local variable [-Wreturn-local-addr] return &a;
|
忽略这个警告运行:收获了一个segmentation falut。说明前面的推理是没问题的,函数栈已经被回收了,再试图去访问里面的内存,当然会报错了。
1 2
| $ ./a.out [1] 60602 segmentation fault (core dumped) ./a.out
|
查阅资料而得:“hello world”不是普通变量,而是字符串常量,字符串常量不是存在函数栈帧中,而是存在内存中的:只读数据段
。
再做一个实验:
还是最开始的代码:
1 2 3 4 5 6 7 8 9
| #include <stdio.h> char *get_string(){ char *string = "hello world"; return string; } int main(int argc, char *argv[]){ char *result = get_string(); printf("%s\n",result); //会输出:hello world }
|
这里我们对其汇编,也就是执行:gcc -c test.c
,让其生成目标文件,也就是未进行链接步骤。这里会生成目标文件:test.o
。
我们利用 objdump
工具查看目标文件里的内容:objdump -s test.o
, -s 参数表示显示所有段的内容,得到如下结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| test.o: file format elf64-x86-64
Contents of section .text: 0000 554889e5 48c745f8 00000000 488b45f8 UH..H.E.....H.E. 0010 5dc35548 89e54883 ec20897d ec488975 ].UH..H.. .}.H.u 0020 e0b80000 0000e800 00000048 8945f848 ...........H.E.H 0030 8b45f848 89c7e800 000000b8 00000000 .E.H............ 0040 c9c3 .. Contents of section .rodata: 0000 68656c6c 6f20776f 726c6400 hello world. Contents of section .comment: 0000 00474343 3a202855 62756e74 7520352e .GCC: (Ubuntu 5. 0010 342e302d 36756275 6e747531 7e31362e 4.0-6ubuntu1~16. 0020 30342e31 31292035 2e342e30 20323031 04.11) 5.4.0 201 0030 36303630 3900 60609. Contents of section .eh_frame: 0000 14000000 00000000 017a5200 01781001 .........zR..x.. 0010 1b0c0708 90010000 1c000000 1c000000 ................ 0020 00000000 12000000 00410e10 8602430d .........A....C. 0030 064d0c07 08000000 1c000000 3c000000 .M..........<... ....0....a....c. .k...... of section .rodata: only> int main(int argc, char *argv[]){ int a = 100; char b = 'j'; float c = 3.14; char *d = "helloworld\n";
printf("a的地址是:%p\n",&a); printf("b的地址是:%p\n",&b); printf("c的地址是:%p\n",&c); printf("d的地址是:%p\n",d); }
|
这里在main中申明几个普通变量:a、b、c和指向字符串常量的d,然后查看他们的地址,运行结果:
1 2 3 4 5
| $ ./a.out a的地址是:0x7ffe8c6e8f98 b的地址是:0x7ffe8c6e8f97 c的地址是:0x7ffe8c6e8f9c d的地址是:0x4006d4
|
可以看到a,b,c地址是紧邻着的,而d的地址则完全不同,也佐证了字符串常量不存于函数栈内存中。
参考资料:《程序员的自我修养——链接、装载与库》</…></stdio.h></stdio.h>