3.动态内存常见的错误
3.1对NUL指针的解应用操作
知识点:
在开辟一块空间时要加上判断是否开辟成功,否则假如没开辟成功的话就会对NULL地址进行解应用(NULL空指针不能进行访问,若访问就会报错:非法访问)
细节:
int main() { int* ptr = (int*)malloc(40); if (ptr == NULL) { perror("malloc"); return 1; } for (int i = 0; i < 10; i++) { *(ptr + i) = i + 1; printf("%d ", *(ptr + i)); } return 0; }
不加(在vs环境下)时会报警告,所以malloc的返回值一定要判断
3.2对动态内存开辟的空间越界访问
知识点:
我们要注意在malloc、realloc中他所要开辟的大小都是以byte为单位的,不能看成你所要开辟的某类型的个数,否则就很可能因为这样而导致越界的问题
细节:
如下面这段错误代码,就是错认为开辟了100个int类型大小的空间
int main() { int* ptr = (int*)malloc(100); for (int i = 0; i < 100; i++) { ptr[i] = 0; } return 0; }
以为开辟了100int的实际上只开辟了25个int(100byte)的空间,所以对于后面的75个空间都是非法访问的
3.3对非动态开辟的内存进行free释放
知识点:
对于free来说只能用来释放堆区上的动态开辟的空间,不能对非动态开辟的内存进行释放(可不能杀疯了)
细节:
int main() { int a = 0;//正常开辟的变量存在栈区上 int *p = &a; free(p); p = NULL; return 0; }
此时因为对非动态内存开辟的空间进行释放,将会导致其程序崩溃。
3.4使用free释放动态内存开辟的一部分空间
知识点:
在用free释放空间时,我们不能只释放开辟的一部分空间,而是应该将所开辟的空间都释放掉,若释放一部分空间同样会报错
细节:
int main() { int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return 1; } for (int i = 0; i < 25; i++) { *p = i; printf("%d ", *p); p++; } free(p); p = NULL; return 0; }
此时因为后置++会有副作用(p = p + 1)会导致其p的地址发生改变,再对p地址进行释放(此时的p没有指到创建时的位置了)就会导致只对一部分动态内存开辟的空间进行释放而导致其程序崩溃。
3.5对同一块动态内存空间多次释放
知识点:
对于已经释放的空间其指向的指针已经变成了野指针若再次释放就会导致报错
细节:
int main() { int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return 1; } // 使用... //释放 free(p); // ... free(p); return 0; }
对于以上这种情况,因为对一个野指针的空间进行释放所以会导致错误;
但是当我们养成良好的代码习惯:在free释放后将p置为NULL ,这样即使再次释放对于free来说当传进来的指针是NULL时就不会进行任何操作。
3.6动态内存空间的忘记释放
知识点:
当对使用完后空间忘记释放时,就会导致一个空间,即使没用了但仍然占着,就会导致空间的浪费。
对此又称:内存泄漏
细节:
void test() { int* p = (int*)malloc(100); if(p == NULL) { perror("malloc"); return; } } int main() { test(); return 0; }
此时因为开辟了一片空间当是并没有对其进行释放,就会导致内存释放;
对此我们有两种解决方法:
1.直接在函数内部释放
void test() { int* p = (int*)malloc(100); if(p == NULL) { perror("malloc"); return; } free(p); p = NULL; } int main() { test(); return 0; }
2.将开辟空间后所用的指针返回并进行接收在主函数内进行释放;
//该函数进行了malloc开辟空间,返回开辟空间的起始地址 //记得后面要释放 int * test() { int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return; } return p; } int main() { int * ptr = test(); free(ptr); ptr = NULL; return 0; }
并且对于这种传递回开辟空间的地址的函数来说最后要进行注释一下,避免别人使用时忘记释放
内存泄漏的危害:
当你不进行内存释放的话,每当你用下该函数就会导致一部分空间被占用,以此往复就会导致内存被占满而导致程序挂掉。
4.动态内存常见问题、笔试题
问题有:非法访问、内存泄漏(具体已标注释)
void GetMemory(char* p)//p有自己的独立空间 { p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(str);//此处为传值调用,就会导致p其实只是str的零时拷贝并不会改变str,所以str并没有开辟好空间,仍然为NULL strcpy(str, "hello world");//因为str仍然为NULL所以就会有非法访问问题(访问了NULL地址) printf(str); } //并且因为p开辟了一个空间且后面也并没有free,也会导致内存泄漏 int main() { Test(); return 0; }
问题:非法访问,当函数调用往后会将函数前所借用的内存空间归还给操作系统。(返回栈空间地址问题)
char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); //此处虽然是p处的地址,但是p指向的空间已经被收回了,所以就会导致访问到别收回的空间非法访问 }
5.C/C++程序在内存中的内存开辟
知识点:
一般来说,在内存中有着:内核区、栈区、堆区、静态区(数据段)、代码段、内存映射段
6.柔性数组
知识点
柔性数组是在c99标准下的
在结构体中的最后一个元素允许是未知大小的数组,这个数组就被称为柔性数组
struct s { int a; char b; char arr[];//柔性数组成员 //char arr[0];写0或者不写0是一样的,数组的大小是未知的, };
细节点:
1.在柔性数组成员前至少有一个成员
2.sizeof返回大小时不包含柔性数组的大小
3.包含柔性数组的结构应该用malloc来进行动态内存分配,并且分配适应的内存并且应该大于结构体的大小,目的是为了适应柔性数组预期的大小 。
struct s *ptr = (struct s*)malloc(sizeof(struct s) + sizeof(char) * 10);//后面开辟的10个char的空间是柔性数组所需的空间 //再通过ptr来访问结构体
并且还可以再通过realloc的方式来增容
柔性数组就好比一个结构体内指针成员,他们都是开辟一块空间,但是用指针成员的话,会相对来说比较麻烦即柔性数组相对于的好处:
不需要内存的单独释放
不需要开辟内存时单独开辟
访问速度较块(没有较多的内存碎片)
若用指针则反之
具体如下:
struct s { int a; char b; char arr[];//柔性数组成员 //char arr[0];写0或者不写0是一样的,数组的大小是未知的, }; struct s1 { int a; char b; char *p; }; int main() { struct s *ptr = (struct s*)malloc(sizeof(struct s) + sizeof(char) * 10); //后面开辟的10个char的空间是柔性数组所需的空间(直接一起开辟) //再通过ptr来访问结构体 printf("%d\n", sizeof(struct s)); free(ptr); ptr = NULL; struct s1* ps = (struct s1*)malloc(sizeof(struct s1)); ps->a = 100; ps->b = 'a'; ps->p = malloc(10 * sizeof(char));//单独开辟 if (ps->p == NULL) { perror("malloc"); return 1; } //使用 .... //单独释放 free(ps->p); ps->p = NULL; return 0; }
本章完。预知后事如何,暂听下回分解。