引言
C语言中复杂的组合数据类型,如数组、字符串、结构体、共用体、枚举等其他组合类型,它们是如何在内存中开辟空间的,以及这些组合数据类型的特点又是如何在内存中体现出来的。现有的操作系统计算机上,为了实现内存的高效利用,操作系统对所有物理内存进行了统一的内存管理,所以应用程序表现出来的都是虚拟内存。
管理方式
在C语言程序中,存放数据所能使用的内存空间大概分为四种情况:栈(stack)、堆(heap)、数据区(.data和.bss区)和常量区(.ro.data)。
栈内存特点
空间实现自动管理:运行时空间自动分配,运行结束空间自动回收。栈是自动管理的,程序员不需要手工干预,方便简单,因此栈又称为自动管理区。
能够被反复使用:栈内存在程序中用的都是一块内存空间,程序通过自动开辟和自动释放,会反复使用这一块空间。
脏内存:栈内存由于反复使用,每次使用后程序不会去清空内容,当下一次该空间再次被分配时,上一次使用的值会还在。
临时性:函数不能返回栈变量的指针,因为该空间在函数运行结束之后就会被释放。
/*函数不能返回局部变量的地址,因为该函数执行完后,存于函数栈的局部变量就已经不存了,如果返回地址
其访问该空间的话,该空间很有可能已经被别人获取正在使用了*/
#include <stdio.h>
int func(void){
/*a是局部变量,分配在栈上又叫栈变量(临时变量)*/
int a = 4;
printf("&a = %p\n",&a);
return &a;
}
void func(void){
int a = 33;
int b = 33;
int c = 33;
printf("int func2, &a = %p\n",&a);
}
int main(void){
int *p = NULL;
p = func();
func2();
func2();
printf("p = %p\n");
printf("*p = %d.\n",*p); //运行之后*p等于33,证明栈内存用完之后是脏的,临时的
return 0;
}
运行结果
&a = 000000000062FDDC
int func2, &a = 000000000062FDD4
int func2, &a = 000000000062FDD4
p = 00007FFDCD56FA30
*p = 0.
func()函数运行结束,函数体内定义的局部变量自动释放,这时看出p地址和&a是不一致的,*p值为0,未初始化的变量默认值为0;
/*栈出溢出:因为操作系统事先给定了栈的大小,
如果在函数中无穷尽的分配局部变量,栈内存总能用完*/
#include <stdio.h>
void stack_overflow(void){
int a[10000000] = {0};
a[10000000 -1] = 12;
return ;
}
//下面函数为递归函数
void stack_overflow2(void){
int a = 2;
stack_overflow2();
return ;
}
int main(void){
//stack_overflow(); //core dumped
stack_overflow2();
return 0;
}
上面两个函数的运行结果Segmentation fault(core dumped)证明栈溢出了。
堆内存特点
灵活:堆时另一种管理形式的内存区域,堆内存的管理灵活。
内存量大:堆内存空间很大,进程可以按需手动申请,使用完手动释放。
程序手动申请和释放:写代码去申请malloc和释放free。
脏内存:堆内存也是反复使用的,而且使用者用完释放前不会清除,因此惹事脏的。
临时性:堆内存在malloc后和free之前的这期间可以被访问。在malloc之前free之后不能访问,否则会有不可预料的后果。
#include <stdio.h>
#include <stdlib.h>
int main(void){
//需要一个1000个int类型元素的数组
//第一步:申请和绑定
int *p = (int *)malloc(1000*sizeof(int));
//第二步:检验申请是否成功
if(p == NULL){
printf("malloc error.\n");
return -1;
}
//第三步:使用申请的内存
*(p+0) = 1;
*(p+1) = 2;
//第四步:释放
free(p);
return 0;
}
如果最后没有将分配的堆内存空间释放的话,这块内存空间会被一直占用,只有当整个程序终止后才会释放。所以对堆内存来说,使用完后及时使用free释放空间就显得非常重要,否则会导致内存泄露,即内存空间都还在 ,但是该空间被之前程序使用后,后续不再使用了,但是它却一直占着。
malloc
使用malloc分配空间时,返回实际上是一个void *类型的指针(地址),该地址是本次申请内存空间的首字节地址,失败返回NULL(使用前需检查是否为NULL),使用完需用free释放。void类型(空类型)不表示没有类型,而表示万能类型,在需要时在具体指定,void*表示是一个无类型指针,对于32位系统,指针本都是4个字节。
值得注意的是:调用free归还p所指向的堆内存之前,指向这段内存的指针p的指向需要发生改变指向了其他的地方的话,必须通过一个中间指针变量先记住p指向的堆空间,之后free时才能通过这个中间变量释放之前p所指向的堆空间,否则就会造成之前p所指的堆空间无法释放,导致内存泄露的发生。
内存中的各个段
代码段、数据段、bss段
编译器在编译程序时,程序会按照一定的结构被划分成各个不同的段进行组织,即.text、.bss、.data段等;
代码段(.text):代码存放程序的代码部分,程序中各种函数的指令就存放在该段;
数据段:又称数据区、静态数据区、静态区,程序中的静态变量空间开辟于此,值得注意:全局变量是整个程序的公共财产,而局部变量只是函数的私有财产。
.bss段:又叫ZI(Zero Initial)段,所有未初始化的静态变量的空间就开辟于此,这个段会自动将未初始化静态空间初始化为0。
注意:数据段(.data)和.bss段实际上没有本质区别,都是用来存放程序中的静态变量,只是.data中存放显式初始化为非0的静态数据,而.bss中存放那些显式初始化为0或者未显式初始化的静态数据。
特殊数据会被放到代码段
#include <stdio.h>
int main(void){
char *p = "linux";
*(p+0) = 'f'; //运行报错,因为是字符串常量,不能被修改
printf("p = %s.\n",p);
return 0;
}
内存管理方式小结
栈、堆和静态这三种内存管理方式都可以为程序提供内存空间。栈空间用于开辟局部变量空间,实现自动内存管理;对于堆内存,程序中需要使用malloc进行手动申请,使用完后必须使用free进行释放,实现手动内存管理;静态数据区的数据段,专门用于开辟全局变量和静态变量,不需要程序员参与管理。
若只是在函数内部临时使用,作用范围希望被局限在函数内部,即定义局部变量;
堆内存和数据段几乎拥有完全相同的属性,大部分时候是可以相互替换。但是他们生命周期不同,堆内存的生命周期是从malloc开始到free结束,而静态变量程序一开始执行就被开辟,直到整个程序结束才回收,伴随程序运行一直存在。所以,若变量只是在程序的一个阶段期间有用,适合使用堆内存空间;若变量需要在程序运行的整个过程中一直存在,适合使用全局变量。
字符串类型
转载:https://blog.csdn.net/qq_40025335/article/details/102475491