数组的元素存储于内存连续位置上。当一个数组被声明时,它所需要的内存在编译时期就会被分配。但是,你也可以使用动态内存分配在运行时为他分配内存。我们如何使用动态内存分配以及怎样进行动态内存分配?
c语言中的内存区域
C语言中内存分为四区:
- 栈区:由编译器自动分配释放,存放函数的参数,局部变量的值。
- 堆区:由程序员分配释放,即动态内存的申请和释放。
- 全局区:全局变量和静态变量是放在一块的。初始化的全局变量和未静态变量在同一个区域,未初始化的全局变量和静态变量在相邻的另外一个区域
- 代码区:存放函数体的二进制代码。
- 文字常量区:常量字符串就是放在这里,程序结束后由系统释放。
这里需要注意的是堆和栈的最主要的区别,栈区是编译期自动释放,无需手动释放,而堆区需要程序员手动释放。
内存分配在堆栈中的区别
内存分配分为两种:
- 静态分配。
静态内存分配,分配内存大小的是固定,问题:1.很容易超出栈内存的最大值 2.为了防止内存不够用会开辟更多的内存,容易浪费内存。
int a[1024 * 1024 * 10];
比如上面的代码就是静态内存分配。
动态分配。
当一个数组被声明时,它所需要的内存在编译时期就被分配。你也可以使用动态内存分配在运行时为它分配内存。
动态内存分配,在程序运行过程中,动态指定需要使用的内存大小,手动释放,释放之后这些内存还可以被重新使用。
malloc 和 free
c函数库中提供了两个函数,malloc和free。分别用来执行动态内存的分配和释放。
这两个函数都在stdlib.h中声明。
void *malloc( size_t size);
void *free(void * pointer);
使用时需要导入
#include <stdlib.h>
malloc
- malloc 参数就是分配的内存字节数,返回的是内存块起始的指针。
- malloc分配一块连续的内存,并不会分开两块或者多块内存的区域。
- 如果内存池是空的,内存无法满足你的请求,就会返回一个NULL指针
free
free参数要么为NULL,要么是malloc,calloc,或者realloc返回的值。
向free传递一个NULL,不会产生效果。
代码示例:
1.创建一个静态数组。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
//栈内存
void stackFun(){
int a[1024];
//栈内存自动释放
}
//堆内存
void heapFun(){
//40M内存
//字节
//void *任意类型的指针
int* p = malloc(1024 * 1024 * 10 * sizeof(int));
//释放
free(p);
}
void main(){
//在堆内存上,分配40M的内存
while (1){
Sleep(1000);
stackFun();
}
getchar();
}
如代码中,我们定义一个while循环,分别对堆内存和栈内存进行分配。我们会发现堆内存会不断上涨。如果使用free,就不会出现这种情况。而栈内存不会增长。
free(p);
2.创建一个数组,动态指定数组的大小
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
//创建一个数组,动态指定数组的大小
void main(){
//静态内存分配创建数组,数组的大小是固定的
//int i = 10;
//int a[i];
int len;
printf("输入数组的长度:");
scanf("%d",&len);
//开辟内存,大小len*4字节
int* p = malloc(len * sizeof(int));
//p是数组的首地址,p就是数组的名称
//给数组元素赋值(使用这一块刚刚开辟出来的内存区域)
int i = 0;
for (; i < len - 1; i++){
p[i] = rand() % 100;
printf("%d,%#x\n", p[i], &p[i]);
}
//手动释放内存
free(p);
getchar();
}
realloc和calloc
realloc 和 calloc的原型如下:
void *calloc( size_t num_elements,size_t element_size);
void *realloc(void *ptr, size_t new_size);
calloc
用于分配内存。malloc和calloc之间的主要区别就是calloc在返回指向内存的指针之前把它初始化为0。
另一个较小的区别就是,calloc的参数包括所需元素的数量和每个元素的字节数。根据这些值,他就能计算出总共需要分配的内存。
realloc
用于修改一个原先已经分配内存的大小,可以使一块内存扩大或者缩小。
重新分配内存分为两种情况:
- 缩小
- 扩大(连续的)
- 如果当前内存段后面有需要的内存空间,直接扩展这段内存空间,realloc返回原指针。
- 如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据库释放掉,返回新的内存地址。
- 如果申请失败,返回NULL,原来的指针仍然有效。
- realloc参数第一个为NULL,那么它的行为就和malloc一样。
代码演示
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
//realloc 重新分配内存
void main(){
int len;
printf("第一次输入数组的长度:");
scanf("%d", &len);
//int* p = malloc(len * sizeof(int));
int* p = calloc(len, sizeof(int));
int i = 0;
for (; i < len; i++){
p[i] = rand() % 100;
printf("%d,%#x\n", p[i], &p[i]);
}
int addLen;
printf("输入数组增加的长度:");
scanf("%d", &addLen);
//内存不够用,扩大刚刚分配的内存空间
//1.原来内存的指针 2.内存扩大之后的总大小
int* p2 = realloc(p, sizeof(int) * (len + addLen));
if (p2 == NULL){
printf("重新分配失败,世界那么大,容不下我。。。");
}
//重新赋值
i = 0;
printf("--------------------------\n");
for (; i < len + addLen; i++){
p2[i] = rand() % 200;
printf("%d,%#x\n", p2[i], &p2[i]);
}
//手动释放内存
/**if (p != NULL){
free(p);
p = NULL;
}
if (p2 != NULL){
free(p2);
p2 = NULL;
}
**/
getchar();
system("pause");
}
结果为:
从图中可以看到数组首地址是不变的。
内存分配注意的几个细节
- 1.不能多次释放
- 2.释放完之后(指针仍然有值),给指针置NULL,标志释放完成
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
void main(){
int len;
printf("输入数组的长度:");
scanf("%d", &len);
int* p = malloc(len * sizeof(int));
int i = 0;
for (; i < len; i++){
p[i] = rand() % 100;
printf("%d,%#x\n", p[i], &p[i]);
}
if (p != NULL){
free(p);
p = NULL;
}
getchar();
}
- 3.内存泄露(p重新赋值之后,再free,并没有真正释放内存)
void main(){
//40M
int* p1 = malloc(1024 * 1024 * 10 * sizeof(int));
//1处。
//free(p1);
//p1 = NULL;
printf("%#x\n",p1);
//80M
p1 = malloc(1024 * 1024 * 10 * sizeof(int) * 2);
free(p1);
p1 = NULL;
getchar();
}
如果1处注释掉。我们来看一下。windows任务管理器。第二个参数为内存占用。
如果把注释打开:
相差40m。