接上一篇文章http://t.csdn.cn/1ONDq,这次我们继续讲解关于动态内存的相关知识。
- #include
- #include
- #include
- int main()
- {
- int* p = (int*)malloc(INT_MAX/4);
- *p = 20;//如果没有足够的空间导致p为NULL,就会有问题
- //所以必须对malloc的返回值进行判断
- free(p);
- p = NULL;
- return 0;
- }
- int main()
- {
- int* p = (int*)malloc(40);
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
- int i = 0;
- for (i = 0; i < 11; i++)
- {
- p[i] = 0;
- //原本只申请了十个整型的空间,但却访问十一个整型
- //所以造成越界访问
- }
- free(p);
- p = NULL;
- return 0;
- }
int main() { int a = 0; int* p = &a; free(p);//p不是动态开辟的空间,不能释放 p = NULL; return 0; }
- int main()
- {
- int* p = (int*)malloc(40);
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
- int i = 0;
- for (i = 0; i < 5; i++)
- {
- p[i] = 0;
- p++;
- }
- //p++导致p不再指向这块空间的起始地址
- //所以如果释放p,等于释放这块空间的一部分(后五个整型空间)
- //这样就会出问题
- free(p);
- p = NULL;
- return 0;
- }
- int main()
- {
- int* p = (int*)malloc(40);
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
- free(p);
- //。。。。。
- free(p);
- //有时候头脑不清醒就可能释放多次,这样就会出问题
- return 0;
- }
即我们动态申请内存后,最后忘记用free释放了,这样就会造成内存泄漏。
源代码:
void GetMemory(char* p) { p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } int main() { Test(); return 0; }问:这段代码运行结果会是什么呢?
例题1解答:
一是没有释放动态内存;
二是会产生这样的错误:
原因是:因为在Test函数中,把str指针置空,然后作为参数传给GesMemory函数,该函数形参用指针p接收,这样指针p也为NULL,然后给指针p动态开辟空间,函数结束,到strcpy函数,但我们注意,我们是给指针p开辟空间,但指针str是没有变的,很多伙伴想不清楚,这不是传址调用吗,p指针变了,str指针也应该跟着变呀?
实则不然,我们应该注意参数是指针也不一定是传址调用,这里是指针之间赋值,应该同时上升一段层次,这里要二级指针才算传址调用,所以指针str是不会变的,还是NULL,既然是NULL,所以就没有足够的空间能放下strcpy的第二个参数,所以报错。
char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { Test(); return 0; }问:这段代码运行结果是什么?
例题2解答:
会产生这样的结果:
“很多小伙伴可能觉得,在GetMemory函数里面返回字符串的起始地址p,所以在Test函数里面用指针str来接收并打印,所以运行结果应该为打印字符串。”
但实则不然,我们一定要注意每个变量的生命周期,数组p的生命周期就只在函数GetMemory里面,所以当该函数return后,里面的变量所占的空间都会被自动销毁(释放),既然p的空间已经被释放了,还赋值给指针str,所以str就是个野指针,再打印str,就造成非法访问内存了。
这类问题属于:返回栈空间地址的问题。
如下图:
C/C++ 程序内存分配的几个区域:1. 栈区( stack ):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。2. 堆区( heap ):一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收。分配方式类似于链表。3. 数据段(静态区)( static )存放全局变量、静态数据。程序结束后由系统释放。4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
实际上普通的局部变量是在 栈区 分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static 修饰的变量存放在 数据段(静态区) ,数据段的特点是在上面创建的变量,直到程序 结束才销毁 ,所以生命周期变长。
- //定义一:
- struct S
- {
- int a;
- int arr[0];
- };
- //定义二:
- struct B
- {
- int a;
- char b;
- int arr[];
- };
1、 结构中的柔性数组成员前面必须至少一个其他成员。2、 sizeof 返回的这种结构大小不包括柔性数组的内存,即sizeof只计算柔性数组前面的成员的大小。3、 包含柔性数组成员的结构用 malloc () 函数进行内存的动态分配,并且分配的内存应该大于结构的大 小,以适应柔性数组的预期大小。
如下例:
struct S { int a; int arr[]; }; int main() { //动态开辟了4+40个字节,因为柔性数组是不会被sizeof计算的 //前面四个字节是给成员a的,后面四十个字节给柔性数组 //因为柔性数组的大小是未知的,我们只需给出预期大小 struct S* str = (struct S*)malloc(sizeof(struct S) + 40); //检查 if (str == NULL) { perror("malloc"); return 1; } //使用 str->a = 10; int i = 0; for (i = 0; i < 10; i++) { str->arr[i] = i + 1; //printf("%d ", str->arr[i]); } //用realloc扩容,因为柔性数组大小未知,是可以改变的 //将之前柔性数组的10个字节的大小扩容到15个 struct S* p = (struct S*)realloc(str, sizeof(struct S) + 60); //检查 if (p == NULL) { perror("realloc"); return 1; } //使用 str = p; for (i = 10; i < 15; i++) { str->arr[i] = i + 1; } //打印 for (i = 0; i < 15; i++) { printf("%d ", str->arr[i]); } //释放 free(str); str = NULL; return 0; }
用malloc函数进行开辟空间,用realloc函数进行扩容,这样数组的大小就是可变的、柔性
的,这就是柔性数组的特点。
1.方便内存释放:如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free 可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free, 所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free 就可以把所有的内存也给释放掉。2.这样有利于访问速度:连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)。