🍍一.对NULL指针的解引用操作
代码:
void test() { int *p = (int *)malloc(INT_MAX/4); *p = 20;//如果p的值是NULL,就会有问题 free(p); }
如果 malloc 开辟空间失败就会形成对 NULL 指针的解引用。
🌽二.对动态开辟空间的越界访问
代码:
void test() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { exit(EXIT_FAILURE); } for (i = 0; i <= 10; i++) { *(p + i) = i;//当i是10的时候越界访问 } free(p); }
malloc开辟的空间与数组一样,只不过是在堆上开辟的空间,但是也是不能越界访问的。例如上述代码我们只开辟了,是个 int 的空间所以我们在访问时就只能访问是个整形。
🏡三.对非动态开辟内存使用free释放
void test() { int a = 10; int* p = &a; free(p);//ok? }
🍐 四.使用free释放一块动态开辟内存的一部分
void test() { int* p = (int*)malloc(100); p++; free(p);//p不再指向动态内存的起始位置 }
这个时候在对空间进行释放就会出错。
🍉 五.对同一块动态内存多次释放
代码:
void test() { int* p = (int*)malloc(100); free(p); free(p);//重复释放 }
🍓六.动态开辟内存忘记释放(内存泄漏)
代码:
void test() { int* p = (int*)malloc(100); if (NULL != p) { *p = 20; } } int main() { test(); while (1); }
忘记释放不再使用的动态开辟的空间会造成内存泄漏。
切记:
动态开辟的空间一定要释放,并且正确释放,虽然代码运行起来不会有什么错误,但是内存会被一点一点吃掉,直到内存吃完了,程序停止。
🍉例题一.
void GetMemory(char* p) { p = (char*)malloc(100); } int main() { char *str=NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
这里的大概思路是,定义一个指针str ,调用GetMemory函数让指针指向 malloc 开辟的空间。再将字符串拷贝到 str 所指向的空间内,最后打印字符串。
但是事实却是:
为什么会出现这样的错误呢?
看似我们让指针指向了 malloc 开辟的空间,但是事实上并没有改变 str 的值,因为形参 p,和实参 str,他俩只是值相同而已,对形参 p 的改变,不会影响实参 str 。所以 str 还是空的。所以后面在调用 strcpy 的时候就会出现对NULL指针的使用错误。程序就会崩掉。
这是这个代码的最大的问题,还有一些小问题,这里的 malloc 的空间并没有释放。
那这个代码改怎么改呢?我们是希望malloc开辟的空间能够交给 str 来指引。意思就是能过改变 str 的指向,使得 str 原本指向NULL,变成malloc开辟的空间。这里就想到了传址调用。所以我们要用str 的地址作为函数参数。形参用二级指针接受。
void GetMemory(char** p) { *p = (char*)malloc(100); } int main() { char* str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str); free(str); str=NULL; return 0; }
还有一些同学应该早早就发现了,这里的打印字符串是不是有错误啊?我们平时不是这样的写的呀。
printf(str);
这里的打印字符串是没有错误的。
🍚 例二:
char* GetMemory(void) { char p[] = "hello world"; return p; } int main() { char* str = NULL; str = GetMemory(); printf(str); return 0; }
大致思路是,创建一个指针 str ,调用函数 GetMemory,函数内部创建一个字符串数组,并返回 数组名。用指针 str 经行接收,并打印。
一切都是那么的合情合理,但是结果总是那么的不尽人意。
简单分析一下,我们知道函数都是在栈上面开辟函数栈帧的,函数内部创建的变量都是局部变量,局部变量随着函数栈帧的销毁而销毁。当函数返回以后就意味着函数的栈帧也就销毁了。里面的局部变量也就不再是有效的局部变量了。就算返回了那块空间的地址,但是那块空间的内容已经无效了。所以才出现这种打印出的乱码。这是一种常见的错误叫做:返回栈空间地址。
🍒例三:
int main() { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); if (str != NULL) { strcpy(str, "world"); printf(str); } return 0; }
这里大家看到代码成功跑起来了,也得到了想要的结果。这总该没问题了吧。
其实这个代码还是有问题的,只不过编译器没有发现而已。
问题在于我们申请的空间,交给 str ,但是已经释放了,free以后就相当于是把malloc申请的空间还给 OS 了,但是 str 还是指向那块空间的。所以 str 仍不为空。 但是这里再将 "world"拷贝给 str,就是有问题的了。
🍊柔性数组:
🍉 (1)定义:
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
实现:
typedef struct st_type { int i; int a[0];//柔性数组成员 }type_a;
有些编译器会报错无法编译可以改成:
typedef struct st_type { int i; int a[];//柔性数组成员 }type_a;
🍉 (2)柔性数组的特点:
1.结构中的柔性数组成员前面必须至少一个其他成员。
2.sizeof 返回的这种结构大小不包括柔性数组的内存。
3.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
🍉 (3)柔性数组的使用
typedef struct st_type { int i; int a[0];//柔性数组成员 }type_a; int main() { //创建结构体,sizeof(int)*5为柔性数组开辟五个整形空间。 type_a* stra = (int*)malloc(sizeof(type_a) + sizeof(int) * 5); stra->i = 100; for (int i = 0; i < 5; i++) { stra->a[i] = i; } for (int i = 0; i < 5; i++) { printf("%d ", stra->a[i]); } printf("stra->i=%d", stra->i); return 0; }
🍭最后:
不是一番寒彻骨,怎得梅花扑鼻香。