程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
程序说明:
//main.cpp
int a = 0; //全局初始化区
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main() {
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c = 0; //全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
//分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
栈的特点:
1.运行时自动分配和自动回收性:栈是自动管理的,程序员不需要手工干预。方便简单。
2.反复使用性:栈内存在程序中其实就是那一块空间,程序反复使用这一块空间。
3.遗留性:栈内存由于反复使用,每次使用后程序不会去清理,因此在使用栈时还是上次栈中遗留下的数值。
4.临时性:(函数不能返回栈变量的指针,因为这个空间是临时的)。
5.溢出性:因为操作系统事先给定了栈的大小,如果在函数中无穷尽的分配栈内存总能用完。
微软雅黑示例
color=#00ffff
堆
堆的作用
对于堆来讲,它是由我们程序员来*分配内存大小的,不过你在给一个指针变量分配内存大小的时候,在主程序return 0 语句之前记得要给它释放,否则会出现不好的影响-------内存泄漏。
怎样分配和释放内存大小呢?
在c语言中,我们经常使用malloc来分配内存大小(不过你分配内存的大小不要超过系统内存大小,不然会报错的,一般系统内存大小是4G,至于为啥是这个,读者可以百度一下这个原因,这里我就不讲了),而使用free函数释放之前分配的内存大小。
void *malloc(size_t size);
void free(void *ptr);
说明
(1)void *是个指针类型,malloc返回的是一个void *类型的指针,实质上malloc返回的是堆管理器分配给我本次申请的那段内存空间的首地址(malloc返回的值其实是一个数字,这个数字表示一个内存地址)。为什么要使用void *作为类型?主要原因是malloc帮我们分配内存时只是分配了内存空间,至于这段空间将来用来存储什么类型的元素malloc是不关心的,由我们程序自己来决定。
(2)什么是void类型。早期被翻译成空型,这个翻译非常不好,会误导人。void类型不表示没有类型,而表示万能类型。void的意思就是说这个数据的类型当前是不确定的,在需要的时候可以再去指定它的具体类型。void *类型是一个指针类型,这个指针本身占4个字节,但是指针指向的类型是不确定的,换句话说这个指针在需要的时候可以被强制转化成其他任何一种确定类型的指针,也就是说这个指针可以指向任何类型的元素。
(3)malloc的返回值:成功申请空间后返回这个内存空间的指针,申请失败时返回NULL。所以malloc获取的内存指针使用前一定要先检验是否为NULL。
(4)malloc申请的内存时用完后要free释放。free(p);会告诉堆管理器这段内存我用完了你可以回收了。
(5)再调用free归还这段内存之前,指向这段内存的指针p一定不能丢(也就是不能给p另外赋值)。因为p一旦丢失这段malloc来的内存就永远的丢失了(内存泄漏),直到当前程序结束时操作系统才会回收这段内存。
malloc的一些细节用法:
malloc(0)的问题(这个没啥意义,不过还是分享一下)。 malloc申请0字节内存本身就是一件无厘头事情,一般不会碰到这个需要。如果真的malloc(0)返回的是NULL还是一个有效指针?答案是:实际分配了20Byte的一段内存并且返回了这段内存的地址。这个答案不是确定的,因为C语言并没有明确规定malloc(0)时的表现,由各malloc函数库的实现者来定义(这个测试了,在不同环境下,确实结果会不一样)。
代码段、数据段、bss段:
编译器在编译程序的时候,将程序中的所有的元素分成了一些组成部分,各部分构成一个段,所以说段是可执行程序的组成部分。
1.什么是代码段
代码段就是程序中的可执行部分,直观理解代码段就是函数堆叠组成的(就是函数体里面的程序那部分)。
2.什么是数据段
(它也被称为数据区、静态数据区、静态区):数据段就是程序中的数据,直观理解就是C语言程序中的全局变量。(注意:全局变量才算是程序的数据,局部变量不算程序的数据(它在栈上),只能算是函数的数据)。
3.什么是bss段
(它又叫ZI(zero initial)段):bss段的特点就是被初始化为0,bss段本质上也是属于数据段,bss段就是被初始化为0的数据段。
数据段(.data)和bss段的区别和联系:
二者本来没有本质区别,都是用来存放C程序中的全局变量的。区别在于把显示初始化为非零的全局变量存在.data段中,而把显式初始化为0或者并未显式初始化(C语言规定未显式初始化的全局变量值默认为0)的全局变量存在bss段。
4.特殊一些要注意的地方
a.有些特殊数据会被放到代码段
1 #include <stdio.h>
2 #include <stdlib.h>
3
4
5 int main(void)
6 {
7 char *p ="linux";
8 //最好这样写const char *p="linux";
9 *(p+0)='f';//原本改为应该是finux
10
11 return 0;
12 }
结果:段错误
说明
C语言中使用char *p = “linux”;定义字符串时,字符串"linux"实际被分配在代码段,也就是说这个"linux"字符串实际上是一个常量字符串而不是变量字符串。
b.显式初始化为非零的全局变量和静态局部变量放在数据段
放在.data段的变量有2种:第一种是显式初始化为非零的全局变量。第二种是静态局部变量,也就是static修饰的局部变量。(普通局部变量分配在栈上,静态局部变量分配在.data段)。
c.未初始化或显式初始化为0的全局变量放在bss段
5.小结
(1)相同点:三种获取内存的方法,都可以给程序提供可用内存,都可以用来定义变量给程序用。
(2)不同点:栈内存对应C中的普通局部变量(别的变量还用不了栈,而且栈是自动的,由编译器和运行时环境共同来提供服务的,程序员无法手工控制);堆内存完全是独立于我们的程序存在和管理的,程序需要内存时可以去手工申请malloc,使用完成后必须尽快free释放。(堆内存对程序就好象公共图书馆对于人,在借书和还书,我们在借书的时候,就从图书馆里借,把借的书看完了,就要归还回图书馆里面去);数据段对于程序来说对应C程序中的全局变量和静态局部变量。
(3)如果我需要一段内存来存储数据,我究竟应该把这个数据存储在哪里? (或者说我要定义一个变量,我究竟应该定义为局部变量还是全局变量还是用malloc来实现)。不同的存储方式有不同的特点,简单总结如下:
函数内部临时使用,出了函数不会用到,就定义局部变量。堆内存和数据段几乎拥有完全相同的属性,大部分时候是可以完全替换的。但是生命周期不一。堆内存的生命周期是从malloc开始到free结束,而全局变量是从整个程序一开始执行就开始,直到整个程序结束才会消灭,伴随程序运行的一生。启示:如果你这个变量只是在程序的一个阶段有用,用完就不用了,就适合用堆内存;如果这个变量本身和程序是一生相伴的,那就适合用全局变量。(堆内存就好象租房、数据段就好象买房。堆内存就好象图书馆借书,数据段就好象自己书店买书)你以后会慢慢发现:买不如租,堆内存的使用比全局变量广泛。下面做一个示例演示
1#include <stdio.h>
2#include <string.h>
3#include <stdlib.h>
4
5
6int a = 5;
7int b;//这里是bss段
8int c = 0;//这里是bss段
9int array[1000];
10
11 char str[] = "linux"; // 第二种方法:定义成全局变量,放在数据段
12
13
14int main(void)
15{
16 char a[] = "linux"; // 第一种方法:定义成局部变量,放在栈上
17
18 char *p = (char *)malloc(10);
19 if (NULL == p)
20 {
21 printf("malloc error.\n");
22 return -1;
23 }
24 memset(p, 0, 10); // 第三种方法: 放在malloc申请的堆内存中
25 strcpy(p, "linux");
26
27 printf("%s\n", a);
28 printf("%s\n", str);
29 printf("%s\n", p);
30 printf("%p\n", a);
31 printf("%p\n", str);
32 printf("%p\n", p);
33
34 free(p);
35return 0;
36}
演示结果: