五. 动态内存经典笔试题分析
说明:以下题目来源于《高质量c++/c编程指南》
5.1 题目1
void GetMemory(char* p) { p = (char*)malloc(100); } void test(void) { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); free(str); } int main() { test(); return 0; }
程序会崩溃,有俩点原因:1、str传的是空指针,p是对str另外一份临时拷贝,p指针指向一片动态内存开辟空间不会影响str,所以下面的函数对null指针进行解引用操作;
2、void GetMemory(char* p)这个函数申请的空间没有进行free操作,导致内存泄漏。
怎么样修改才对呢?
第一种改法:
void GetMemory(char** p) { *p = (char*)malloc(100); } void test( ) { char* str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str); str = NULL; } int main() { test(); return 0; }
此时*p就是str,修改*p就是修改str.
第二种改法:
#include<string.h> char* GetMemory() { char* p = (char*)malloc(100); return p; } void test( ) { char* str = NULL; str= GetMemory(); strcpy(str, "hello world"); printf(str); free(str); str = NULL; } int main() { test(); return 0; }
此时对于GetMemory()传参没有什么用处,所以可以不用传参。
5.2 题目2
char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { Test(); return 0; }
局部变量所申请的空间随函数调用结束,返还给操作系统,所以str所指向的空间不属于主函数,属于非法访问。
属于返回栈空间地址问题(str属于野指针)
5.3 题目3
void GetMemory(char** p, int num) { * p = (char*)malloc(num); } void Test(void) { char* str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } int main() { Test(); return 0; }
这个和第一道差不多,可以正常打印出来,但是没有free,容易造成内存泄漏。
5.4 题目
void Test(void) { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); if (str != NULL) { strcpy(str, "world");//非法访问 printf(str); } } int main() { Test(); return 0; }
因为str指向的空间已经释放了,str为野指针,属于非法访问。
六 柔性数组
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构体中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做『柔性数组』成员。
例如:
typedef struct st_type 2 { 3 int i; 4 int a[];//柔性数组成员 5 }type_a;
或者
typedef struct st_type 2 { 3 int i; 4 int a[0];//柔性数组成员 5 }type_a;
6.1 柔性数组的特点
• 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。 (至少俩个成员)
• sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。
• 包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的大小,以适应柔性数组的预期⼤⼩。
举例:
代码1:
struct st_type { int i; int a[0];//柔性数组成员 }; int main() { printf("%d\n", sizeof(struct st_type));//输出的是4 return 0; } int main() { int i = 0; struct st_type* p = (struct st_type*)malloc(sizeof(struct st_type) + 100 * sizeof(int)); //业务处理 p->i = 100; for (i = 0; i < 100; i++) { p->a[i] = i; } free(p); return 0; }
6.2 柔性数组的优势
代码2:
struct st_type { int i; int* p_a; }; int main() { struct st_type* p = (struct st_type*)malloc(sizeof(struct st_type)); p->i = 100; p->p_a = (int*)malloc(p->i * sizeof(int)); int i = 0; //业务处理 for (i = 0; i < 100; i++) { p->p_a[i] = i; } //释放空间 free(p->p_a); p->p_a = NULL; free(p); p = NULL; return 0; }
上述 代码 1 和 代码 2 可以完成同样的功能,但是⽅法 1 的实现有两个好处:
第一个好处是:方便内存释放
如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给用户。⽤户调⽤free可以释放结构体,但是⽤户并不知道这个结构体内的成员也需要free,所以你不能 指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返 回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。
第二个好处是:这样有利于访问速度.
连续的内存有益于提⾼访问速度,也有益于减少内存碎片。
七. 总结C/C++中程序内存区域划分
C/C++程序内存分配的⼏个区域:
1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时 这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内 存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。栈区的详解可看(函数栈帧的创建与销毁)
2. 堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统回收 。分配⽅式类似于链表。
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。