内存管理:
1.C语言运行时的内存分配
2.static关键字
1.修饰变量
局部变量:
<1>在编译的过程中,会在数据区为该变量开辟空间,如果代码中未对其进行初始化,则系统默认初始化为0。
<2>用static修饰的局部变量,会延长局部变量的生命周期
#include <stdio.h>
void fun(){
static int var= 10;
var++;
printf("var=%d\n",var);}
int main(){
fun();
fun();
fun();
return 0;
}
修饰全局变量
<1>static修饰全局变量,会在数据区域分配存储空间,如果全局变量未初始化,编译器会自动初始化为0.
<2>static修饰全局变量,会限制全局变量的使用范围,让其只能在本文件使用,其他文件不能使用。
#include <stdio.h>
static int global_var = 10;
main.c
#include <stdio.h>
extern int global_var;
void fun(){
printf("global_var = %d\n", global_var);
return;
}//此时就不能被正常使用
2.修饰函数:
修饰函数后函数变为静态函数
<1>静态函数只能在声明它的文件中使用,其他文件不能引用该函数
<2>不同的文件可以使用相同名字的静态函数,互不影响
动态内存分配:
固定开辟的内存大小在程序运行时我们无法准确掌握,因此就需要进行动态开辟内存
<1>malloc和free:
如果开辟成功,则返回一个指向开辟空间的指针如果开辟失败,则返回一个NULL指针,因此malloc的返回值要进行检查返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用时由使用者决定如果参数size=0,malloc的行为是标准未定义的,取决于编译器
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
int main() {
int* p =(int*) malloc(sizeof (int)*10);
//动态开辟内存
if(NULL==10){
printf("%s\n", strerror(errno));//用于判断内存开辟是否失败
} else{
for (int i = 0; i < 10; i++) {
*(p + i) = i + 1;//进行赋值
} //打印
for (int i = 0; i < 10; i++) {
printf("%-4d", *(p + i));
}
printf("\n");
}
return 0;
}
void free(void* memblock)
- 如果参数memblock指向的空间不是动态开辟的,那么free函数的行为是未定义的
- 如果参数memblock是NULL指针,则函数什么也不做
Tips:
<1>C语言中可以创建变长数组,C99标准支持
<2> malloc和free都声明在stdlib.h的头文件中
<2>calloc:
void* calloc(size_t num,size_t size);
与malloc不同的是calloc会返回地址之前把申请的每个字节初始化为0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main() {
// 向内存申请15个整型类型的空间
int* p = (int*)calloc(10, sizeof(int));
//判断是否开辟成功
if (NULL == p) {
// 打印错误信息
printf("%s\n", strerror(errno));
}
else {
// 给10个空间进行赋值并打印
for (int i = 0; i < 10; i++) {
*(p + i) = i + 1;
}
// 打印
for (int i = 0; i < 15; i++) {
printf("%-4d", *(p + i));
}
printf("\n");
// 释放空间
free(p);
p = NULL;
}
return 0;
}
<3>realloc:
realloc函数会让动态内存管理更灵活,调整对应的内存地址大小
原型:void* realloc(void* ptr,size_t size)
- ptr是要调整的内存地址
- size 调整之后的内存大小
- 返回值为调整之后的内存起始地址
- 这个函数会将原内存中的数据迁移到新的内存空间
- 这个函数会将原内存空间自动释放
- realloc在调整动态分配的内存空间大小时,有两种情况
<1>如果原指针指向的空间之后有足够的内存空间可以追加,则直接追加
<2>如果原指针指向的空间之后没有足够的内存空间,该函数会重新找一块新的内存区域,按照指定大小重新开辟空间。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main() {
// 向内存申请5个整型类型的空间
int* p = (int*)calloc(5, sizeof(int));
//判断是否开辟成功
if (NULL == p)
{
// 打印错误信息
printf("%s\n", strerror(errno));
} else {
//给5个空间进行赋值并打印
for (int i = 0; i < 5; i++) {
*(p + i) = i + 1;
}
// 在这里20个字节的空间不能满足要求,需要进行调整
int* np = (int*)realloc(p, 40);
for (int i = 5; i < 10; i++) {
*(np + i) = i + 10;
}
//打印
for (int i = 0; i < 10; i++) {
printf("%-4d", *(np + i));
}
printf("\n");
// 释放空间
free(np);
np=NULL;
}
return 0;
}
常见的动态内存错误:
1. 对NULL指针的解引用操作
int main(){
int* p = (int*)malloc(-1);
*p = 20;
// 如果分配失败,P=NULL,此时就会报错
free(p);
p = NULL;
return 0;
}
2. 对动态开辟空间的越界访问
int main() {
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p) {
exit(EXIT_FAILURE);
} else {
for (int i = 0; i <= 10; i++) {
*(p + i) = i + 1;
// 越界访问 }
}
free(p);
p = NULL;
return 0;
}
3. 使用free释放一块动态开辟内存的一部分
int main() {
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
exit(EXIT_FAILURE);
} else {
p++;
free(p);
// p不再指向动态内存的起始地址
p = NULL;
}
return 1;
}
4. 对非动态开辟的空间使用free函数
int main() {
int a = 10;
int* p = &a;
free(p);
// p不是动态开辟的空间
p = NULL;
return 0;
}
5. 对同一块动态内存多次释放
int main() {
int* p = (int*)malloc(20);
if (NULL == p) {
exit(EXIT_FAILURE);
}
free(p);
free(p);
//重复释放
p = NULL;
return 0;
}
// 如何避免重复释放
int main() {
int* p = (int*)malloc(20);
if (NULL == p) {
exit(EXIT_FAILURE);
}
free(p);
p = NULL;
// 释放完成后,将指针置为NULL
free(p); // free(NULL)时,此函数啥功能都不做
return 0;
}
6. 动态开辟的内存忘记释放(内存泄露)
int main() {
while (1) {
malloc(10);
}
return 1;
}