在运用C语言实现静态通讯录功能(等总结完会给大家单独写一篇博客,大家有想学习的话可以点进去看看)结束后,为了实现动态通讯录功能需学习C语言有关动态内存开辟的知识,接下来就跟大家分享一下。
1.C/C++程序内存分配区域及为什么会存在动态内存分配?
2.malloc,calloc,realloc,free的使用
3.常见的动态内存开辟错误
4.知识拓展之柔性数组
(1)柔性数组特点
(2)柔性数组的使用
(3)柔性数组的优势
(1)C/C++程序内存分配区域
如图:
具体详解:
1)栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配效率很高(置于处理器的指令集),但是分配的内存容量有限。其主要存放运行函数而分配的局部变量,函数参数,返回数据,返回地址等。
2)堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
3)数据段(静态区):存放静态数据及全局变量。程序结束后由系统释放。
4)代码段:存放函数体(类成员函数和全局函数)的二进制代码。
(2)为什么会存在动态内存分配?
我们可以定义数组开辟一串连续的字节空间也可以定义任意数据类型的变量开辟空间。但是大家想想就可以知道其开辟空间的大小都是固定的,且数组在声明时,必须指定数组长度。在内存编译时才会分配空间。可是在实现通讯录功能时是不可能提前预知到底会有多少联系人的,只能在程序运行的时候才会知道,初始数组空间大小要么太小要么太大,这时候在编译时开辟空间就无法满足需求了。
(1)malloc库函数入参及返回值:
malloc功能描述:分配给定字节数的一整块连续的空间,返回空间起始位置的地址。
返回值情况如下:
1)开辟成功,则返回开辟好空间的起始指针
2)开辟失败,则返回空指针(NULL)
3)当size为0,malloc的行为是C语言标准未定义的,取决于具体编译器
这里大家可能有一些疑问?返回值类型void*是什么数据类型,其实是malloc库函数不知道具体开辟空间的类型,在使用时由调用者强转成自己所需类型的指针。且在使用完毕开辟成功后的空间,一定要用free函数(☆只能对动态开辟的空间进行释放)进行空间释放。
(2)free函数入参及返回值:
注:如果ptr为空指针,则不对其进行任何操作。
示例代码:
//动态内存开辟之malloc示例
int main() {
int* ptr = (int*)malloc(40);
//malloc可能开辟失败,所以得判断ptr是否为空
if (ptr == NULL) return 0;
//使用完毕后进行释放
free(ptr);
ptr = NULL;//free不会把ptr置空,让ptr不在指向已经被释放完的空间起始地址
return 0;
}
(3)calloc函数入参及返回值:
calloc功能描述:跟malloc一样,不同的是它会把指定字节大小的连续空间存放的初始值设为0。
其第一个参数为要分配的元素个数,第二个是每个元素的字节大小。
示例代码:
//calloc示例
int main() {
int* ptr = (int*)calloc(5, 4);
if (ptr == NULL) return 0;
//使用完毕后进行释放
free(ptr);
ptr = NULL;
}
调试打开监视可以看到5个int类型的元素初始值都为0
(4)realloc函数入参及返回值
calloc功能描述:对由malloc或calloc开辟的空间进行调整。
调整方式如图:
注:不要直接将realloc开辟空间的起始地址直接赋值给需要调整的空间起始地址,这样做是为了在realloc开辟失败时,防止把原来malloc或calloc开辟好的空间地址给置空了。
示例代码:
//calloc示例基础上进行realloc示例
int main() {
int* ptr = (int*)calloc(5, 4);
if (ptr == NULL) return 0;
//ptr = (int*)realloc(ptr, 10);//不能直接将ptr修改,否则开辟失败,则ptr会置空
int* Rptr = (int*)realloc(ptr, 10);
if (Rptr == NULL) return 0;
else ptr = Rptr;
//使用完毕后进行释放
free(ptr);
ptr = NULL;
}
(1)对NULL指针的解引用操作
int main() {
int* p = (int*)malloc(INT_MAX / 4);
*p = 100;//如果开辟失败,则对NULL进行解引用并赋值时会出错
free(p);
p=NULL;
return 0;
}
(2)对动态开辟空间的越界访问
int main() {
int* ptr = (int*)malloc(40);//开辟存放了10个数据类型为int的元素
if (ptr == NULL) return 0;
//当偏移量为10时会造成越界访问,相对于访问数组最后一个元素的下一个位置
*(ptr + 10) = 10;
free(ptr);
ptr = NULL;
return 0;
}
(3)对非动态开辟内存使用free释放
int main() {
int num = 10;
int* ptr = #
free(ptr);//ptr根本就不是动态内存开辟的空间
return 0;
}
(4)使用free释放一块动态开辟内存的一部分
int main() {
int* ptr = (int*)malloc(40);
ptr++;
//ptr++后不再指向动态内存开辟的起始位置,所以动态开辟的空间并没有完全被释放
free(ptr);
return 0;
}
(5)对同一块动态内存多次释放
int main() {
int* ptr = (int*)malloc(40);
free(ptr);
free(ptr);//重复释放
return 0;
}
(6)动态开辟内存忘记释放,造成内存泄漏
void test() {
int* ptr = (int*)malloc(40);
if (ptr == NULL) return;
*(ptr + 1) = 5;
//忘记释放ptr
}
int main() {
test();
int a = 4, b = 7, c = 0;
c = a + b;
return 0;
}