在冯诺依曼的体系结构中,一个进程必须有:代码段,堆栈段,数据段。
进程的虚拟地址空间图示如下:
堆栈段:
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;
-
}
![](https://i-blog.csdnimg.cn/blog_migrate/f66ba41866f19beaf0d58e328a460b2b.jpeg)
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类似:前者返回指针的之前把分配好的内存内容都清空为零;后者改变一个指针所指向的内存块的大小,可以扩大和缩小,它经常把内存拷贝到别的地方然后将新地址返回。
![](https://i-blog.csdnimg.cn/blog_migrate/2f4e58999b64c4b20b68919d2273f4ca.jpeg)
栈、堆辨析:
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,避免以后错误地使用p1
-
-
p2=
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: 8048550
-
-
bss Locatoin:
-
The bss_var is at address: 8049b54
-
-
data Location:
-
The data_var is at address: 8049b40
-
-
Stack 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: 8049b50
-
-
The string p in stack start is at address: ff9cdf70
-
The string p in stack end is at address: ff9cdf90
-
-
Heap Location:
-
The allocated heap start is at address: 9020078
-
The allocated heap end is at address: 90200c8
-
-
p,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
}
void *alloca(size_t size);
alloca在栈中分配size个内存空间(函数返回时自动释放掉空间,无需程序员手动释放),并将空间初始化为0。
转载自:https://blog.csdn.net/ywcpig/article/details/52303745