一、我们来看一道百度面试题:
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.首先,我们由main函数调用进入test(void)函数内部
char* str = NULL;//创建一级指针变量str,str变量内部存放空指针NULL且无法进行解引用操作
2.GetMemory(str);
str以值传递形式传给GetMemory(char *p),p相当于str的一份临时拷贝,
p里面存放的是str变量的值NULL
3.1接下来进入GetMemory函数内部,一开始p==NULL,malloc函数在堆上开辟一块内存空间并将空间地址赋给指针p来维护,我们假设地址为0x0012ff40
3.2GetMemory()函数调用完毕,内部形参变量p销毁,不再记录开辟的内存块地址,此时str仍然存放空指针NULL,并没有指向一块有用的空间
strcpy(str,"Hello World");
这里给strcpy函数传入空指针NULLs,程序崩溃,无法运行(访问非法内存)
总结:
- 运行代码会出现崩溃现象
- 程序存在内存泄漏问题
str以值传递形式给了GetMemory函数的形参p
GetMemory函数返回之后开辟的动态内存空间尚未释放且无法找到
改进:
void GetMemory(char**p) {//用二级指针接收str地址
*p = (char*)malloc(100);
}//p销毁,str记录开辟空间地址
void test(void) {
char* str = NULL;
GetMemory(&str);//传str指针
strcpy(str,"Hello World");
printf(str);
if (str != NULL) {
free(str);//释放内存
str == NULL;
}
else {
return;
}
}
int main() {
test();
return 0;
}
二、几个经典的面试题
题目2:返回栈空间地址问题
char* GetMemory(void) {
char p[] = "hello world";
return p;
}
void Test(void) {
char* str = NULL;
str = GetMemory();
prinf(str);
}
int main() {
Test();
return 0;
}
请问:Test()函数的输出结果是什么?
我的答案:程序崩溃,GetMemory()执行完毕p会被销毁,str为空指针(错了)
正确答案:输出一个随机值
GetMemory()函数内部在栈上开辟一块内存空间存放字符数组 hello world,p为内存空间地址并返回给str(这里返回的是一个栈空间的地址),str成功存储这块空间地址,GetMemory()调用完毕生命周期结束,形参p销毁并将开辟的空间还给操作系统,此时仍然可以通过str找到这块内存,但这块空间内存放的是我们不清楚的一个随机值
类似的,
void* test() {
//static int a = 10;//静态区地址返回--无error
int a = 10;//栈区地址返回
return &a;
}
int main() {
int *p= test();
//a销毁前将栈区地址返回给p,test()调用完生命周期结束并将这块空间还给操作系统
//p指向这块空间但属于非法访问(野指针)
*p = 20;//error
return 0;
}
void* test() {
int* p = malloc(100);//堆区地址返回
return p;
}
int main() {
int* ptr = test();
//p销毁前将开辟空间地址返回给ptr,ptr可以找到这块空间并且这块空间没有被还给操作系统,堆空间只有free后才会被回收
return 0;
}
题目3:
void GetMemory(char** p, int num) {
*p = malloc(num);//堆区地址返回
}
void Test() {
char* str = NULL;
GetMemory(&str, 100);//地址传递--str记录堆上开辟内存块地址
strcpy(str, "hello");
printf(str);
}
int main() {
Test();
return 0;
}
请问Test()函数运行后会有什么结果?
我的答案:hello
题目4:
void Test(void) {
char* str = (char*)malloc(100);//str维护堆上动态开辟的一个100个字节的一块内存空间
strcpy(str, "hello");//使用
free(str);//free释放str指向的空间后,并不会把str置为空指针NULL,str成为野指针
//str=NULL;//改错
if (str != NULL) {//异常--非法访问野指针
strcpy(str, "world");
printf(str);
}
}
int main() {
Test();
return 0;
}
//篡改动态内存区的内容,后果难以预测,非常危险
请问Test()函数运行后会有什么结果?
我的答案:输出hello,然后程序异常–访问野指针
三、C/C++程序的内存开辟
C/C++程序内存分配的几个区域:
1.栈区(stack) :在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
⒉堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由*OS**(operate system 操作系统)*回收。分配方式类似于链表。
3.数据段(静态区)<(static)存放全局变量、静态数据。程序结束后由系统释放。4.代码段:存放函数体(类成员函数和全局函数)的二进制代码。
有了这幅图,我们就可以更好的理解在《C语言初识》中讲的static关键字修饰局部变量的例子了。
实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁
所以生命周期变长。