程序内存空间(代码段、数据段、堆栈段)
在冯诺依曼的体系结构中,一个进程必须有:代码段,堆栈段,数据段。
进程的虚拟地址空间图示如下:
堆栈段:
1. 为函数内部的局部变量提供存储空间。
2. 进行函数调用时,存储“过程活动记录”。
3. 用作暂时存储区。如计算一个很长的算术表达式时,可以将部分计算结果压入堆栈。
数据段(静态存储区):
包括BSS段(Block Started by Symbol)的数据段。BSS段存储未初始化或初始化为0的全局变量、静态变量,具体体现为一个占位符,并不给该段的数据分配空间,只是记录数据所需空间的大小。数据段存储经过初始化的全局和静态变量。
#define DEBUG "debug" int space[1024][1024];
int data = 1;
int no_data = 0; int main()
{ char *a = DEBUG; return 1;
}
使用nm查看后
0000000000600660 d _DYNAMIC
00000000006007f8 d _GLOBAL_OFFSET_TABLE_
0000000000400578 R _IO_stdin_used w _Jv_RegisterClasses
0000000000600640 d __CTOR_END__
0000000000600638 d __CTOR_LIST__
0000000000600650 D __DTOR_END__
0000000000600648 d __DTOR_LIST__
0000000000400630 r __FRAME_END__
0000000000600658 d __JCR_END__
0000000000600658 d __JCR_LIST__
0000000000600820 A __bss_start
0000000000600818 D __data_start
0000000000400530 t __do_global_ctors_aux
00000000004003e0 t __do_global_dtors_aux
0000000000400580 R __dso_handle w __gmon_start__
0000000000600634 d __init_array_end
0000000000600634 d __init_array_start
0000000000400490 T __libc_csu_fini
00000000004004a0 T __libc_csu_init U __libc_start_main@@GLIBC_2.2.5
0000000000600820 A _edata
0000000000a00840 A _end
0000000000400568 T _fini
0000000000400358 T _init
0000000000400390 T _start
00000000004003bc t call_gmon_start
0000000000600820 b completed.6347
000000000060081c D data
0000000000600818 W data_start
0000000000600828 b dtor_idx.6349
0000000000400450 t frame_dummy
0000000000400474 T main
0000000000600830 B no_data
0000000000600840 B space
可以看到变量data被分配在data段,而被初始化为0的no_data被分配在了BSS段。
.bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);而.data却需要占用,其内容由程序初始化。
注意:.data和.bss在加载时合并到一个Segment(Data Segment)中,这个Segment是可读可写的。
代码段:
又称为文本段。存储可执行文件的指令;也有可能包含一些只读的常数变量,例如字符串常量等。
.rodata段:存放只读数据,比如printf语句中的格式字符串和开关语句的跳转表。也就是你所说的常量区。例如,全局作用域中的 const int ival = 10,ival存放在.rodata段;再如,函数局部作用域中的printf("Hello world %d\n", c);语句中的格式字符串"Hello world %d\n",也存放在.rodata段。
但是注意并不是所有的常量都是放在常量数据段的,其特殊情况如下:
1)有些立即数与指令编译在一起直接放在代码段。
int main()
{ int a = 10; return 1;
}
a是常量,但是它没有被放入常量区,而是在指令中直接通过立即数赋值
2)对于字符串常量,编译器会去掉重复的常量,让程序的每个字符串常量只有一份。
char *str = "123456789";
char *str1 = "helloworld"; int main()
{ char* a = "helloworld"; char b[10] = "helloworld"; return 1;
}
汇编代码如下:
.file "hello.c"
.globl str .section .rodata
.LC0: .string "123456789" .data .align 8 .type str, @object .size str, 8
str: .quad .LC0
.globl str1 .section .rodata
.LC1: .string "helloworld" .data .align 8 .type str1, @object .size str1, 8
str1: .quad .LC1 .text
.globl main .type main, @function
main:
.LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movq $.LC1, -8(%rbp) movl $1819043176, -32(%rbp) movl $1919907695, -28(%rbp) movw $25708, -24(%rbp) movl $1, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc
.LFE0: .size main, .-main .ident "GCC: (GNU) 4.4.6 20110731 (Red Hat 4.4.6-3)" .section .note.GNU-stack,"",@progbits
可以看到str1和a同时指向.rodata段中同一个LC1
3)用数组初始化的字符串常量是没有放入常量区的。
4)用const修饰的全局变量是放入常量区的,但是使用const修饰的局部变量只是设置为只读起到防止修改的效果,没有放入常量区。
5)有些系统中rodata段是多个进程共享的,目的是为了提高空间的利用率。
注意:程序加载运行时,.rodata段和.text段通常合并到一个Segment(Text Segment)中,操作系统将这个Segment的页面只读保护起来,防止意外的改写。
堆:
就像堆栈段能够根据需要自动增长一样,数据段也有一个对象,用于完成这项工作,这就是堆。堆区域是用来动态分配的内存空间,用 malloc 函数申请的,用free函数释放。calloc、realloc和malloc类似:前者返回指针的之前把分配好的内存内容都清空为零;后者改变一个指针所指向的内存块的大小,可以扩大和缩小,它经常把内存拷贝到别的地方然后将新地址返回。
栈、堆辨析:
1、栈区(stack):由编译器自动分配释放 ,存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap):由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
程序示例:
1、举个例子说明各种变量存放在什么区:
#include <stdlib.h>int a=123; //a在全局已初始化数据区 char *p1; //p1在BSS区(未初始化全局变量) int main()
{int b; //b为局部变量,在栈区 char s[]="abc"; //s为局部数组变量,在栈区 //"abc"为字符串常量,存储在已初始化数据区 char *p1,*p2; //p1,p2为局部变量,在栈区 char *p3="123456"; //p3在栈区,"123456"在常量区(.rodata)static int c=456; //c为局部(静态)数据,在已初始化数据区 //静态局部变量会自动初始化(因为BSS区自动用0或NULL初始化)p1=(char*)malloc(10); //分配得来的10个字节的区域在堆区 p2=(char*)malloc(20); //分配得来的20个字节的区域在堆区 free(p1);free(p2);p1=NULL; //显示地将p1置为NULL,避免以后错误地使用p1p2=NULL;
}
2、我们再写一个程序,输出各变量的内存空间:
#include <stdio.h>
#include <stdlib.h>extern void afunc(void);extern etext,edata,end;int bss_var;//未初始化全局变量存储在BSS段int data_var=42;//初始化全局存储在数据段#define SHW_ADR(ID,I) printf("The %s is at address: %8x\n",ID,&I);//打印地址宏int main(int argc,char *argv[])
{char *p,*b,*nb;printf("etext address: %8x\tedata address: %8x\tend address: %8x\t\n",&etext,&edata,&end);SHW_ADR("main",main);//查看代码段main函数位置SHW_ADR("afunc",afunc);//查看代码段afunc函数位置printf("\nbss Locatoin:\n");SHW_ADR("bss_var",bss_var);//查看BSS段变量地址printf("\ndata Location:\n");SHW_ADR("data_var",data_var);//查看数据段变量地址printf("\nStack Loation:\n");afunc();printf("\n");p=(char*)alloca(32);//从栈中分配空间if(p!=NULL){SHW_ADR("string p in stack start",*p);SHW_ADR("string p in stack end",*(p+32*sizeof(char)));}b=(char*)malloc(32*sizeof(char));//从堆中分配空间nb=(char*)malloc(16*sizeof(char));//从堆中分配空间printf("\nHeap Location:\n");SHW_ADR("allocated heap start",*b);//已分配的堆空间的起始地址SHW_ADR("allocated heap end",*(nb+16*sizeof(char)));//已分配的堆空间的结束地址printf("\np,b and nb in stack\n");SHW_ADR("p",p);//显示栈中数据p的地址SHW_ADR("b",b);//显示栈中数据b的地址SHW_ADR("nb",nb);//显示栈中数据nb的地址free(b);//释放申请的空间,以避免内存泄露free(nb);
}void afunc(void)
{static int level=0;//初始化为0的静态数据存储在BSS段中int stack_var;//局部变量,存储在栈区if(++level==5)return;SHW_ADR("stack_var in stack section",stack_var);SHW_ADR("leval in bss section",level);afunc();
}/* Output
etext address: 80488bf edata address: 8049b48 end address: 8049b58
The main is at address: 80485be
The afunc is at address: 8048550bss Locatoin:
The bss_var is at address: 8049b54data Location:
The data_var is at address: 8049b40Stack Loation:
The stack_var in stack section is at address: ff9cdf80
The level in bss section is at address: 8049b50
The stack_var in stack section is at address: ff9cdf50
The level in bss section is at address: 8049b50
The stack_var in stack section is at address: ff9cdf20
The level in bss section is at address: 8049b50
The stack_var in stack section is at address: ff9cdef0
The level in bss section is at address: 8049b50The string p in stack start is at address: ff9cdf70
The string p in stack end is at address: ff9cdf90Heap Location:
The allocated heap start is at address: 9020078
The allocated heap end is at address: 90200c8p,b and nb in stack
The p is at address: ff9cdfac
The b is at address: ff9cdfa8
The nb is at address: ff9cdfa4
*/
内存管理函数:
这里插入一段对void*的解释:
void*这不叫空指针,这叫无确切类型指针.这个指针指向一块内存,却没有告诉程序该用何种方式来解释这片内存.所以这种类型的指针不能直接进行取内容的操作.必须先转成别的类型的指针才可以把内容解释出来.
还有'\0',这也不是空指针所指的内容.'\0'是表示一个字符串的结尾而已,并不是NULL的意思.
真正的空指针是说,这个指针没有指向一块有意义的内存,比如说:
char* k;
这里这个k就叫空指针.我们并未让它指向任意内存.
又或者
char* k = NULL;
这里这个k也叫空指针,因为它指向NULL也就是0,注意是整数0,不是'\0'.
一个空指针我们也无法对它进行取内容操作.
空指针只有在真正指向了一块有意义的内存后,我们才能对它取内容.也就是说要这样
k = "hello world!";
这时k就不是空指针了.
void *malloc(size_t size)
(typedef unsigned int size_t;)
malloc在内存的动态存储区中分配一个长度为size字节的连续空间,其参数是无符号整型,返回一个指向所分配的连续空间的起始地址的指针。分配空间不成功(如内存不足)时返回一个NULL指针。
void free(void *ptr)
free释放掉内存空间。
void *realloc(void *ptr,size_tsize)
当需要扩大一块内存空间时,realloc试图直接从堆的当前内存段后面获得更多的内在空间,并返回原指针;如果空间不够就使用第一个能够满足这个要求的内存块,并将当前数据复制到新位置,释放原来的数据块;如果申请空间失败,返回NULL。
void *calloc(size_t nmemb, size_t size)
calloc是malloc的简单包装,它把动态分配的内存空间进行初始化,全部清0。此函数的实现描述:
void *calloc(size_t nmemb, size_t size)
{
void *p;
size_t total;
total=nmemb*size;
p=malloc(total);
if(p!=NULL)//申请空间
memset(p,'\0',total);//初始化\0
return p;
}
void *alloca(size_t size);
alloca在栈中分配size个内存空间(函数返回时自动释放掉空间,无需程序员手动释放),并将空间初始化为0。
Reference:
https://en.wikipedia.org/wiki/Data_segment
程序内存空间(代码段、数据段、堆栈段)相关推荐
- php程序内存空间,php如何管理内存
内存管理一般会包括以下内容: 是否有足够的内存供我们的程序使用: 如何从足够可用的内存中获取部分内存: 对于使用后的内存,是否可以将其销毁并将其重新分配给其它程序使用.(推荐学习:PHP编程从入门到精 ...
- 【汇编语言-3】 代码、数据、堆栈在同一个段
(1) 代码段中使用数据 编程计算8个数据的和,结果保存在ax中.代码如下: code segment assume cs:code dw 0123H,0456H,0789H,00bcH,00 ...
- 进程内存空间五种 数据区
另外,我们可以看一下: 堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减.当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张) ...
- linux进程的堆栈空间_代码段(指令,只读)、数据段(静态变量,全局变量)、堆栈段(局部变量)、栈【转】...
转自:http://blog.csdn.net/gongweijiao/article/details/8207333 原文参见:http://blog.163.com/xychenbaihu@yea ...
- java数据段 静态区_linux进程的堆栈空间_代码段(指令,只读)、数据段(静态变量,全局变量)、堆栈段(局部变量)、栈【转】...
一)概述 .堆栈是一个用户空间的内存区域,进程使用堆栈作为临时存储. .堆栈中存放的是函数中的局部变量,在函数的生命周期中可以将变量压入堆栈,编译器需要确保堆栈指针在函数退出前恢复到初始位置,也就是说 ...
- linux c 代码分析工具,编程达人 分享几款Linux 下C/C++程序内存泄漏检查工具
1.内存管理是否正确(因为这个程序本身开辟很多内存空间进行缓存管理,同时这个程序程序本身就是基于C/C++开发的,内存管理机制一直是程序员头痛的东西) 2.程序的健硕性如何(服务器任何程序的基本要求就 ...
- Unix下C程序内存泄露检测工具:valgrind的安装使用
Valgrind是一款用于内存调试.内存泄漏检测以及性能分析的软件开发工具. Valgrind的最初作者是Julian Seward,他于2006年由于在开发Valgrind上的工作获得了第二届Goo ...
- 段选择符和段描述符的关系
段选择符用来表示指向哪个段描述符,即用来在段描述符中寻址,前13位是地址,能寻0到(2^13)-1,因此段描述符表的大小就是 8192,他还牵扯到一些特权级的限制,后三位:段描述符是用来表示这个段的一 ...
- C语言动态申请内存空间之malloc(),calloc(),realloc()函数
在C语言中用于动态申请内存空间的函数主要为malloc()函数,calloc()函数,以及realloc()函数,以下主要介绍三个函数的用法,区别以及使用时的注意事项. malloc(),calloc ...
最新文章
- python 条件选择语句_Python趣味入门4:选择往往是最重要的-条件语句
- ECCV2020优秀论文汇总|涉及点云处理、3D检测识别、三维重建、立体视觉、姿态估计、深度估计、SFM等方向...
- [云炬商业计划书阅读分享]
- 历届试题 打印十字图
- 欧拉定理(洛谷-P5091)(扩展欧拉定理实现)
- oracle怎么删除存储,删除Oracle分区存储是一个怎样的过程?
- 技术:叫我怎么爱上你
- ERROR: Couldn't connect to Docker daemon at http+docker://localunixsocket - is it running?
- python窗口大小动态变化_python – 如何让tkinter画布动态调整窗口宽度?
- Java文件File类型转BASE64
- 度量衡计算工具_度量衡计量单位换算转换器
- DiskGenius格式化磁盘、U盘
- 国家区块链漏洞库 《区块链漏洞定级细则》发布
- python requests simplejson.errors.JSONDecodeError: Expecting value报错
- 外贸网络营销搜索引擎和B2B模式比较
- 快速学会3DMax高级建模人物骨骼蒙皮
- 7-2 大小写字母转换
- 步入J2EE架构和过程
- 灰关联分析与语音/音乐信号识别
- Android post参数太长请求失败问题解决
热门文章
- 尼科彻斯定理(简单易懂)
- 记——通过点击表头弹出筛选选项列表,点击进行数据筛选
- Vue报错-npm ERR,missing script: serve npm ERR, A complete log of this run can be found in:
- 【数据库】MySQL数据库约束(六大约束)
- AMD GPU 实用工具 rocm-smi 使用方法
- 大数据学习系列(九)Hadoop1.X痛点分析及Hadoop2.X提出的解决方案
- 加载组件Already included file name ‘e:/Vuejs/组件/单文件组件/vue_test/src/components/Message.vue‘ differs from
- 树莓派拓展模拟量采集(AD)功能
- MATLAB常用指令及解释(持续更新中)
- [hdu6595]Everything Is Generated In Equal Probability