几个经典的笔试题
1.
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; }
程序运行起来之后直接崩溃了。
在这之前,我们再来看一段程序:
#include<stdio.h> int main() { char* str = "abcdef"; printf("%s\n", str); printf(str); printf("abcdef"); return 0; }
程序运行起来之后会造成程序崩溃,而且程序存在内存泄露的问题。
那为什么会造成程序泄露呢?
str以值传递的形式给p
p是GetMemory函数的形参,只能函数内部有效。
等GetMemory函数返回之后,动态开辟内存尚未释放并且无法找到,所以会造成内存泄漏。
那我们如何解决问题呢?
法一(利用参数):
#include<stdio.h> #include<string.h> #include<stdlib.h> void GetMemory(char **p) { *p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str); free(str); str = NULL; } int main() { Test(); return 0; }
法二(利用返回值):
#include<stdio.h> #include<string.h> #include<stdlib.h> char* GetMemory(char* p) { p = (char*)malloc(100); return p; } void Test(void) { char* str = NULL; str = GetMemory(str); strcpy(str, "hello world"); printf(str); free(str); str = NULL; } int main() { Test(); return 0; }
2.
#include<stdio.h> char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf("str"); } int main() { Test(); return 0; }
请问运行之后会有什么现象?
我们可以看到结果是一些随机值。
首先这是一个返回栈空间地址的问题,栈空间的地址是不能随意返回的,否则就会出现问题。,局部变量是放在栈区上的,栈区的这块空间一旦出了这块空间就会销毁,既然销毁了,但依然把这块空间地址返回去,这是没有意义的。
所以在一个函数内部返回栈空间的地址是有问题的,返回来之后如果有指针记得住这块地址,然后盲目的去访问这个地址所指向的空间的时候,此时就造成了非法访问了,因为这块空间已经还给操作系统了。另外这块空间里面存放的是什么我们其实是不知道的,所以打印出来的是一个随机值。
再看一个与这道题类似的:
#include<stdio.h> //这也是一个返回栈空间地址的问题 int* test() { int a = 10;//栈区 return &a; } int main() { int* p = test(); *p = 20; return 0; }
我们可以这样写:
#include<stdio.h> int* test() { static int a = 10;//a此时在静态区 return &a; } int main() { int* p = test(); *p = 20; return 0; }
即把int a = 10;的前面加上static,就是static int a = 10;这样的话static修饰局部变量的时候,它的声明周期就变长了,此时变量a出来函数范围后不会销毁,即这块内存空间还存在,那这个时候,指针p就能找到那块空间,*p就把里面的值给为了20。另外局部变量a被static修饰后就不放在栈上了,而是放在静态区中。
总之,返回栈空间地址是有危险的。
还有一种写法:
#include<stdio.h> int* test(void) { int* ptr = malloc(100);//堆区 return ptr; } int main() { int* p = test(); return 0; } //注意这里没有释放空间
补充一点
int* f2(void) { int* ptr; *ptr = 10; return ptr; }
这是个错误的写法。因为*ptr是一个野指针,并没有初始化,默认其内部放的是一个随机值,对其进行解引用(ptr=10)的话就相当于访问一个随机的空间,就会造成非法访问内存。
3.
#include<stdio.h> 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; }
这段代码的问题就是没有释放空间,我们可以这样改进:
4.
#include<stdio.h> #include<stdlib.h> #include<string.h> void test() { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); if (str != NULL) { strcpy(str, "world"); printf(str); } } int main() { test(); return 0; }
这段代码考察的主要是free,要注意:free释放str指向的空间后,并不会把str置为NULL。free(str);之后,str成为野指针,if(str!=NULL)不起作用。所以这段代码存在非法访问内存的问题。我们需要在free释放空间后并把str置为NULL。请看:
#include<stdio.h> #include<stdlib.h> #include<string.h> void test() { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); str = NULL; if (str != NULL) { strcpy(str, "world"); printf(str); } } int main() { test(); return 0; }
额外知识(C/C++程序的内存开辟)
int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; int num1[10] = { 1,2,3,4 }; char char2[] = "abcd"; char* pChar3 = "abcd"; int* ptr1 = (int*)malloc(sizeof(int) * 4); int* ptr2 = (int*)calloc(4, sizeof(int)); int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); free(ptr1); free(ptr3); }
C/C++程序内存分配的几个区域:
1.栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束后这些存储单元自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数分配的局部变量、函数参数、返回数据、返回地址等。
2.堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
3.数据段(静态区)(static)存放全局变量、静态数据、程序结束后由系统释放。
4.代码段:存放函数体(类成员函数和全局函数)的二进制代码。
实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量放在数据段(静态区),数据段的特点是在上面创建的变量,知道程序结束才销毁,所以生命周期变长。
柔性数组
柔性数组(flexible array)。在C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做【柔性数组】成员。
例如(首先来看第一种写法):
typedef struct st_type { int i; int a[0];//柔性数组成员(作为结构体的最后一个元素,大小是未知的) }type_a;
第二种写法:
struct S { int n; int arr[0];//未知大小的柔性数组成员(与上面那种写法的形式不一样,但都是一个意思) }; int main() { struct S s; return 0; }
柔性数组的柔性体现在这个数组的大小是可变的。
我们该如何使用柔性数组呢?
#include<stdio.h> struct S { int n; int arr[0];//与上面那种写法的形式不一样,但都是一个意思 }; int main() { struct S s; printf("%d\n", sizeof(s)); return 0; }
运行结果如下:
在包含柔性数组成员的这个结构体中,当我们计算其大小时,是不包含这个柔性数组成员的。
既然柔性数组的大小是可变的,那我们如何调整其大小呢?
#include<stdio.h> struct S { int n; int arr[0];//与上面那种写法的形式不一样,但都是一个意思 }; int main() { /*struct S s; printf("%d\n", sizeof(s));*/ struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int)); return 0; }
其实当我们写出struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));这段代码的时候就相当于我们开辟了一块空间,空间大小为24个字节。
接下来我们来访问这块空间:
1
#include<stdio.h> #include<stdlib.h> struct S { int n; int arr[0];//与上面那种写法的形式不一样,但都是一个意思 }; int main() { /*struct S s; printf("%d\n", sizeof(s));*/ struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int)); ps->n = 100; int i = 0; for (i = 0; i < 5; i++) { ps->arr[i] = i; } return 0; }
此时如果我们想继续调整这块空间呢?
#include<stdio.h> #include<stdlib.h> struct S { int n; int arr[0];//与上面那种写法的形式不一样,但都是一个意思 }; int main() { /*struct S s; printf("%d\n", sizeof(s));*/ struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int)); ps->n = 100; int i = 0; for (i = 0; i < 5; i++) { ps->arr[i] = i; } struct S* ptr = realloc(ps, 44); if (ptr != NULL) { ps = ptr; } for (i = 5; i < 10; i++) { ps->arr[i] = i; } for (i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } //释放 free(ps); ps = NULL; return 0; }
以上代码是我们控制数组大小的一种方式(柔性数组)。
接下来看另外一种方式(动态开辟数组):
#include<stdio.h> #include<stdlib.h> #include<string.h> struct S { int n; int* arr; }; int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); ps->arr = malloc(5 * sizeof(int)); int i = 0; for (i = 0; i < 5; i++) { ps->arr[i] = i; } for (i = 0; i < 5; i++) { printf("%d ", ps->arr[i]); } //调整大小 int* ptr = realloc(ps->arr, 10 * sizeof(int)); if (ptr != NULL) { ps->arr = ptr; } for (i = 5; i < 10; i++) { ps->arr[i] = i; } for (i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } //两次释放(有先后顺序) free(ps->arr); ps->arr = NULL; free(ps); ps = NULL; return 0; }
既然有柔性数组的概念,那么柔性数组一定有它的优势。
柔性数组特点
- 结构中的柔性数组成员前面必须至少一个其它成员。
- sizeof返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小。
柔性数组好处
第一:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事,所以我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户只需要一次free就可以把所有的内存也给释放掉。
第二:有利于访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片。
可以参照大佬的文章:C语言结构体里的成员数组和指针
以上就是动态内存管理的全部内容,有一点点多。😁但的确很重要,需要我们循序渐进的去学习并且消化吸收。
好了,下次见吧,再见啦!!!