c和c++编程语言基础:程序的段,堆与栈

在硬件里面,内存单元需要占用CPU的地址,但是不是所有的地址都对应着内存单元。

因为在嵌入式里,通常情况下内存都不会很大,不会把CPU的地址占用完的.

:s5p6818芯片里,内存单元最大只能占用2G地址.


一个进程理论上可以访问4G地址.进程在系统里是独立的,进程间的空间是不能跨进程访问的.

如果在进程里访问的地址不属于此进程的则会发生段错误.


通常情况下,书上提的Memory其实是指“地址”


int num; &num是取地址.

指针也是变量的一种,它用于存放其它变量的地址,不要忘了它本身也有地址.

如果函数的参数是指针,调用此函数时提供的参数值为地址.

如果函数的返回值类型为指针,则表示此函数是返回一个地址,接收此函数的返回值时用相同类型指针变量来存放返回地址.

程序就是编译出来的镜像,处于执行状态的程序叫进程.一个程序可以执行多次,每次执行会产生一个进程.


//

程序镜像里有分成很多个段:


段其实就是在程序镜像文件里从一个位置到另一个位置范围里存放某种数据,便于系统在加载程序生成进程时的处理.

我们最需要注意的段:.text .rodata .data .bss


具体查看一个程序镜像里的段的内容可以通过反汇编查看:

objdump-D a.out


.text段用于统一存放整个程序的代码指令


.rodata
段用于统一存放整个程序的只读的数据内容.只读数据段里的内容只能读,如作写操作则会发生段错误.

存放只读的全局变量的值,字符串常量的值, 静态的只读的局部变量的值.(静态的局部变量可看成限制访问范围的全局变量)

代码:

[cpp]  view plain  copy
  1. 1  
  2.   
  3. 2const int num = 0x99;  
  4.   
  5. 3int main(void)  
  6.   
  7. 4{  
  8.   
  9. static const int kk = 0x77;  
  10.   
  11. char *p = "000111aaabbb";  
  12.   
  13. 7  
  14.   
  15. 8 p[2] = 0; //会发生段错误  
  16.   
  17. return 0;  
  18.   
  19. 10}  
  20.   
  21. 反汇编查看.rodata:  
  22.   
  23. Disassemblyof section .rodata:  
  24.   
  25.   
  26. 0000000000400590<_IO_stdin_used>:  
  27.   
  28. 400590: 01 00 add %eax,(%rax)  
  29.   
  30. 400592: 02 00 add (%rax),%al  
  31.   
  32. 400594: 00 00 add %al,(%rax)  
  33.   
  34. ...  
  35.   
  36.   
  37. 0000000000400598<__dso_handle>:  
  38.   
  39. ...  
  40.   
  41.   
  42. 00000000004005a0<num>:  
  43.   
  44. 4005a0: 99 cltd // const int num = 0x99;  
  45.   
  46. 4005a1: 00 00 add %al,(%rax)  
  47.   
  48. 4005a3: 00 30 add %dh,(%rax) // 30表示0x30,即48,'0'的ascii码  
  49.   
  50. 4005a5: 30 30 xor %dh,(%rax)  
  51.   
  52. 4005a7: 31 31 xor %esi,(%rcx)  
  53.   
  54. 4005a9: 31 61 61 xor %esp,0x61(%rcx)  
  55.   
  56. 4005ac: 61 (bad)  
  57.   
  58. 4005ad: 62 62 (bad)  
  59.   
  60. 4005af: 62 (bad)  
  61.   
  62. 4005b0: 00 00 add %al,(%rax)  
  63.   
  64. ...  
  65.   
  66.   
  67. 00000000004005b4<kk.1725>: // static const int kk = 0x77;  
  68.   
  69. 4005b4: 77 00 ja 4005b6 <kk.1725+0x2>  



.rodata段的作用是便于系统管理进程的只读数据,防止误改操作.


.data段用于存放初始化过的全局变量的值,初始化过的静态局部变量值.注意初始化值为零时,不算是初始化过的.

代码:

[cpp]  view plain  copy
  1. 1  
  2.   
  3. 2int num = 0x99;  
  4.   
  5. 3int main(void)  
  6.   
  7. 4{  
  8.   
  9. static int aa = 0x77;  
  10.   
  11. 6  
  12.   
  13. 7  
  14.   
  15. return 0;  
  16.   
  17. 9}  
  18.   
  19.   
  20. 反汇编:  
  21.   
  22. Disassemblyof section .data:  
  23.   
  24.   
  25. 0000000000601028<__data_start>:  
  26.   
  27. 601028: 00 00 add %al,(%rax)  
  28.   
  29. ...  
  30.   
  31.   
  32. 000000000060102c<num>: //int num = 0x99;  
  33.   
  34. 60102c: 99 cltd  
  35.   
  36. 60102d: 00 00 add %al,(%rax)  
  37.   
  38. ...  
  39.   
  40.   
  41. 0000000000601030<aa.1725>: //static int aa = 0x77;  
  42.   
  43. 601030: 77 00 ja 601032 <aa.1725+0x2>  




.rodata段与.data段在编译时,编译器就会在程序镜像中分配出把需的空间,.rodata段和.data段的内容越多,程序的镜像就会越大.

证明:

如上面的程序镜像大小:

[root@localhost1705]# ll a.out

-rwxr-xr-x1 root root 8520 May 4 11:01 a.out


修改源码:

[cpp]  view plain  copy
  1. 1  
  2.   
  3. 2int num = 0x99;  
  4.   
  5. 3char data[1024*1024*10] = {1};  
  6.   
  7. 4int main(void)  
  8.   
  9. 5{  
  10.   
  11. static int aa = 0x77;  
  12.   
  13. 7  
  14.   
  15. 8  
  16.   
  17. return 0;  
  18.   
  19. 10}  
  20.   
  21.   
  22. 编译后查看:  
  23.   
  24. [root@localhost1705]# ll a.out  
  25.   
  26. -rwxr-xr-x1 root root 10494389 May 4 11:05 a.out  
  27.   
  28.   
  29.   
  30. .bss段用于存放未初始过的全局变量,静态的局部变量.  
  31.   
  32. 代码:  
  33.   
  34. int num = 0;  
  35.   
  36. int main(void)  
  37.   
  38. 4 {  
  39.   
  40. static int aa = 0;  
  41.   
  42. 6  
  43.   
  44. return 0;  
  45.   
  46. 8 }  
  47.   
  48.   
  49. 反汇编代码:  
  50.   
  51. Disassembly of section .bss:  
  52.   
  53.   
  54. 000000000060102c<__bss_start>:  
  55.   
  56. 60102c: 00 00 add %al,(%rax)  
  57.   
  58. ...  
  59.   
  60.   
  61. 0000000000601030 <num>:  
  62.   
  63. 601030: 00 00 add %al,(%rax)  
  64.   
  65. ...  
  66.   
  67.   
  68. 0000000000601034 <aa.1725>:  
  69.   
  70. 601034: 00 00 add %al,(%rax)  
  71.   
  72. ...  



注意bss段在编译时不会分配具体所需的空间,系统加载程序生成进程时才会具体分配空间的。

证明:

修改源码前的大小:

[root@localhost1705]# ll a.out

-rwxr-xr-x1 root root 8512 May 4 11:10 a.out


修改源码:

[cpp]  view plain  copy
  1. 1  
  2.   
  3. 2int num = 0;  
  4.   
  5. 3char data[1024*1024*10] = {0};  
  6.   
  7. 4int main(void)  
  8.   
  9. 5{  
  10.   
  11. static int aa = 0;  
  12.   
  13. 7  
  14.   
  15. return 0;  
  16.   
  17. 9}  
  18.   
  19.   
  20. 反汇编代码:  
  21.   
  22. Disassemblyof section .bss:  
  23.   
  24.   
  25. 0000000000601040<completed.6337>:  
  26.   
  27. ...  
  28.   
  29.   
  30. 0000000000601060<num>:  
  31.   
  32. ...  
  33.   
  34.   
  35. 0000000000601080<data>:  
  36.   
  37. ...  
  38.   
  39.   
  40. 0000000001001080<aa.1726>:  
  41.   
  42. …  



.bss段里仅仅是记录有哪些变量及所需的空间,不会存放具体的值.


除了这些段以后,我们还需关注堆和栈


(stack):用于分配局部变量和函数的参数的空间区域,分配出来的空间地址是从高往低分配的.

代码:

[cpp]  view plain  copy
  1. 1 #include <stdio.h>  
  2.   
  3. 2  
  4.   
  5. int main(int argc, char*argv[])  
  6.   
  7. 4 {  
  8.   
  9. int num = 77;  
  10.   
  11. int aa = 88;  
  12.   
  13. 7  
  14.   
  15. 8 printf("%p,%p\n", &num, &aa);  
  16.   
  17. 9  
  18.   
  19. 10 return 0;  
  20.   
  21. 11 }  
  22.   
  23.   
  24. 编译执行后的输出:  
  25.   
  26. [root@localhost 1705]# ./a.out  
  27.   
  28. 0x7ffd6eccab6c, 0x7ffd6eccab68  


进程只有一个栈.一个函数执行时,从栈里分配局部变量的空间,函数执行结束时会回收栈里分配的空间。回收的空间会重用的。

代码:

[cpp]  view plain  copy
  1. 1 #include <stdio.h>  
  2.   
  3. 2  
  4.   
  5. void func()  
  6.   
  7. 4 {  
  8.   
  9. int a = 44, b = 55;  
  10.   
  11. 6 }  
  12.   
  13. 7  
  14.   
  15. void func2()  
  16.   
  17. 9 {  
  18.   
  19. 10 int aa, bb;  
  20.   
  21. 11  
  22.   
  23. 12 printf("%d,%d\n", aa, bb);  
  24.   
  25. 13 }  
  26.   
  27. 14  
  28.   
  29. 15 int main(int argc, char*argv[])  
  30.   
  31. 16 {  
  32.   
  33. 17 func();  
  34.   
  35. 18 func2();  
  36.   
  37. 19  
  38.   
  39. 20 return 0;  
  40.   
  41. 21 }  
  42.   
  43.   
  44. 编译执行后的输出:  
  45.   
  46. [root@localhost 1705]# ./a.out  
  47.   
  48. 44,55  


在系统里每个进程在栈里所使用的大小空间有限制(cenos7上不超过8M).

代码:

[cpp]  view plain  copy
  1. 1 #include <stdio.h>  
  2.   
  3. 2  
  4.   
  5. 3 #define LEN (8*1024*1024)  
  6.   
  7. int main(int argc, char*argv[])  
  8.   
  9. 5 {  
  10.   
  11. char data[LEN];  
  12.   
  13. 7  
  14.   
  15. 8 data[LEN-1] = 'c';  
  16.   
  17. 9  
  18.   
  19. 10 printf("%c\n",data[LEN-1]);  
  20.   
  21. 11  
  22.   
  23. 12 return 0;  
  24.   
  25. 13 }  



编译执行后会发生段错误.我们要注意是整个进程只能使用8M的栈大小空间,如果函数嵌套调用时,每层函数都占用栈的空间,则有可以超出8M.所以避免爆栈,尽量使用动态分配空间。



(heap):系统里用于动态分配空间的区域,分配出来的空间地址是从低往高分配的。

系统里只有一个堆。每个进程在堆里使用的空间是没有限制大小,只要系统还有可用内存即可申请成功。

动态分配出来的空间需要调用释放函数回收。

C: malloc() --- free()

C++: new --- delete

java号称最安全的语言,其实就是因为每个变量基本都是动态分配出来的.


本文由广州尚观科技发布,广州尚观,专业的Linux、Java大数据、嵌入式开发、HTML5培训机构。有兴趣的可以关注扫下方二维码加关注,可领取免费精品视频一份。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值