动态内存分配相关笔试题
Suzhou 25 1

内存泄漏:

void GetMemory(char* p)
{
    p = (char*)malloc(100);
    //free(p);
    //p = NULL;
}
void Test(void) 
{
    char* str = NULL;
    GetMemory(str);
    strcpy(str, "hello world"); 
    printf(str);
}
int main()
{
    Test();
    return 0;
}

上述代码中,主函数调用test()函数,创建一个空指针变量str,然后传值给函数GetMemory()函数,指针变量p接收,p也是空指针,malloc开辟100个字节的空间并将开辟空间的地址赋给p,然后离开GetMemory函数,局部变量p被销毁,内存中开辟的空间的地址并没有传给str也没有被释放,发生内存泄漏。 代码第11行将“hello world”拷贝到str所指向的内存空间中,NULL不是有效地址,并没有指向有效空间,强行拷贝发生对NULL的解引用,遍历NULL试图拷贝,遍历过程中会访问非法内存,程序在这里崩溃。

上述代码修改:

#include <string.h>
#include <stdlib.h>
void GetMemory(char** p)
{
    *p = (char*)malloc(100);
}
void Test(void) 
{
    char* str = NULL;
    GetMemory(&str);
    if (str == NULL)
    {
        return 0;
    }
    strcpy(str, "hello world"); 
    printf(str);
    free(str);
    str = NULL;
}
int main()
{
    Test();
    return 0;
}

另一种改正方法是将GetMenory中的p返回到Test函数中,使用str接收返回值,即接收动态内存开辟的地址。


返回栈空间地址(错误):

char* GetMemory(void) 
{
    char p[] = "hello world";
    return p;
}
void Test(void)
{
    char* str = NULL; 
    str = GetMemory();
    printf(str);
}
int main()
{
    Test();
    return 0;
}

::: tip 代码分析:主函数调用Test函数,新建空指针str,调用GetMemory,创建p的数组,并将p的地址返回给Test函数中的str。数组p是局部范围内的数组,只能在函数GetMemory内部使用,离开该函数后数组p生命周期结束,str不能通过存储的地址找到p指向的字符串。 ::: ::: warning 局部变量创建在栈区,离开创建的范围就会销毁,返回栈空间的地址是有问题的,此时该地址指向的不再是创建的局部变量,有可能是其他数据,会造成非法访问数据的问题。 :::

返回静态区的地址(正确):

int* Test()
{
    int a = 0;  //栈区
    return &a;
}
int main()
{
    int* p = Test();
    *p = 20;
    return 0;
}

如果使用static修饰局部变量数组时,static修饰的变量数组放在静态区,生命周期变长,离开函数后并不销毁,*p = 20就可以正确赋值。

返回堆区的地址(正确):

int* Test()
{
    int p = malloc(100);
    return &p;
}
int main()
{
    int* ptr = Test();
    return 0;
}

上述代码中,malloc开辟的内存空间是在堆区的,离开Test函数后,变量p被销毁,但malloc开辟的内存空间并不会销毁,p将该空间的地址返回给ptr,通过ptr可以访问到该空间。 堆区创建的空间只有free命令才可以回收。


非法访问内存

int Hi()
{
    int* p;
    *p = 20;
    return 0;
}

上述代码中指针p没有初始化,p指向的空间是随机值,对随机值赋值容易出现非法访问内存的问题。p没有指向有效的内存空间,称为野指针。


void GetMemory(char** p,int num) 
{
    *p = (char*)malloc(num);
}
void Test(void) 
{
    char* str = NULL;
    GetMemory(&str,100); 
    strcpy(str, "hello");
    printf(str);
    //改进
    //free(str);
    //str = NULL;
}
int main() 
{
    Test(); 
    return 0;
}

上述代码中,malloc开辟的空间地址传给 * p, * p就是void str,即新开辟的内存空间地址放在str中,是可以执行strcpy的操作的。但是在开辟空间使用完毕后,没有释放该空间,会导致内存泄漏。


释放后的空间被使用:

#include <stdlib.h>
#include <string.h>
void Test(void)
{
    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;
}

上述代码中if判断语句不起作用,原因是free(str)后,str变为野指针。 ::: tip Test函数创建变量str存储malloc开辟的100字节内存空间,将“hello\0”拷贝到str指向的空间,即在malloc开辟的内存空间中,free(str)的效果是将malloc开辟的内存空间释放掉,虽然str中仍存有该空间地址,但是无法访问该地址。free命令不会将str置为空指针,if判断为真,再将“world”拷贝到str中去,执行的结果是该内存空间中的“hello”被“world”覆盖。 但是,str指向的空间已经被free命令释放,该空间已不再属于str,再对该内存空间进行拷贝和打印操作,就涉及非法访问内存。 :::


总结: C/C++程序的内存开辟方式 ::: tip 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。 堆区 ( heap ):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。 数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。 代码段︰存放函数体(类成员函数和全局函数)·的二进制代码。 :::

评论区

索引目录