首页 > 系统相关 >【C语言】动态内存经典笔试题(上卷)

【C语言】动态内存经典笔试题(上卷)

时间:2024-06-08 11:29:07浏览次数:10  
标签:动态内存 void 笔试 C语言 char 地址 str GetMemory 指针

前言

本系列将详细讲解4道有关动态内存的经典笔试题,以助于加深对动态内存的理解。这些题目都非常经典,你可能随时会遇到它们,所以非常重要

本文讲解其中的前两题。

第一题

这个程序运行的结果是什么?

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;
}
分析

代码的执行顺序从主函数开始,主函数中调用了Test(),所以我们去看Test()。创建了空指针str传给函数GetMemory(),开辟了100字节的地址,首地址存到p中。再回到Test(),往下执行。

要注意的是,实参传给形参时,形参是实参的一份临时拷贝。所以相当于我们在GetMemory里创建了一个指针变量p,里面存的是NULL,因为p得到的是str的值;在堆上申请了100个字节的空间,假设地址是0x0012ff40,然后这个地址就放入p中,p有能力能找到这块空间。

但此时并没有将这个地址给str,str依然是NULL,GetMemory函数结束后,str还是NULL,没有指向有效的空间,据我们对strcpy的了解,已经对空指针解引用操作了,程序会崩溃。这是这段代码的第一个问题。

第二个问题,我们没有free我们在GetMemory里开辟的这块空间,回到Test之后没人记得这块空间在哪,就形成了内存泄漏的问题。

补充:这个printf(str);的语法是没有问题的。虽然我们一般会使用printf("%s\n",str);的方式,但其实printf("haha\n");我们也是直接把字符串给printf了,本质上我们给的是首字符的地址。就像char*p = "haha";我们也是把字符首地址赋给p而不是把字符赋给p。  也就是printf有首字符地址就可以打印。

怎么改正这个代码?

我们可以去思考这个代码的原意是什么,从函数名GetMemory可以看出,是想申请(get)一块内存(memory),将这块内存用于存放拷贝内容。所以我们可以从原意出发去修改。

改法1:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void GetMemory(char** p)//二级指针接收
{
    *p = (char*)malloc(100);//*p相当于str,str=(char*)malloc(100);开辟的内存首地址确实给了str
}

void Test(void)
{
    char* str = NULL;
    GetMemory(&str);//改为传str的地址
    strcpy(str, "hello world");
    printf(str);
    free(str);//避免内存泄漏
    str=NULL;
}

int main()
{
    Test();
    return 0;
}

为了理解这么改的含义,我们可以简化一下这个问题,如果我们写这样的代码:

 这就类似我们上面修改前的代码,p只是a的一份临时拷贝,里面放着0,当它改为10后,对a并没有实质影响。那么如果我们想通过改变p来改变a的值,就会把代码改成这样:

可以看到,此时我们的a打印出来,已经被改为10了。

同样的,str虽然是指针,但指针变量也是变量。我们现在是想将str里的内容(NULL)改为我们开辟的内存的起始地址,所以我们直接传str是不能达到效果的,应该传&str,就像&a,但是不同的在于str本身就是个指针,而指针的地址应该是个二级指针,所以我们要用二级指针来接收。

最后,再记得释放str和置为空指针,避免内存泄漏就行了。

改法2:

我们把p返回并用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;
}
分析:

打印结果是这个:

这题的突破口在于,p可是个数组,进入函数GetMemory创建,出了函数就会销毁。而在销毁前,我们先把p返回了。p是数组名,即起始地址。这个地址返回后我们放到str中,str能找到这块空间。内存里确实有这块空间,但是能否用这块空间取决于我们有没有使用权限。出了函数GetMemory,其实这块空间的使用权限就没有了,但str仍能找到它(str就是个野指针)。打印出来“烫烫烫…”说明这块空间使用权限不属于当前程序时,空间中内容可能被改掉了,我们非要打印就不再是hello world了。

注意,这个现象发生根本原因在于我们的p是个数组,如果改为melloc开辟的空间,就不会有这个问题。因为如果我们不手动释放,在程序彻底结束前,p就一直指向这块空间且有使用权限。数组(也相当于局部变量)开辟的空间是放在上的。所以这个问题是一个典型的返回栈空间(临时空间)地址的问题。

我们可以返回一个变量,但不能返回一个变量的地址。因为这个变量(比如数组)出了函数就销毁了,记住地址也没用。

比如,下面这个代码就是返回一个变量:

实际上,编译器会怎么处理这个代码呢?

a会销毁,编译器会把10放到一个寄存器中。寄存器是CPU里的一个存储空间,是不会销毁的。a销毁了没关系,会将寄存器里的10再给n。就像一个托管。

而如果返回地址,就不行:

 

此时p得到返回的地址,但是指向的空间已经归还操作系统,所以p是个野指针。虽然我们打印出来发现还是原来的值,这只是巧合。如果改为这样:

可以看到,在前面打印了一次其他内容后,就无法再打印出2077。 为什么呢?这说明我们在打印*p时,这个空间还给操作系统了,里面已经不再是2077了。我们在printf("See\n");的时候可能已经把这块空间申请走了,把原本内容覆盖了。

 

可以看到,在test函数结束后,函数栈帧销毁了,而printf("See\n");在申请空间时可能把原来test的空间覆盖了,所以可能会把p指向的空间的内容修改。

再次重申,这是返回栈空间地址的问题。

一旦有人接收了这个地方,就注定是个野指针。

附加题

我们再看看这两个题:

一、以下程序有什么问题?

int* f1(void) {
	int x = 10;
	return (&x);
}

分析:这个也是返回栈空间地址的问题。

二、以下程序有什么问题?

int* f2(void) {
	int* ptr;
	*ptr = 10;
	return ptr;
}

分析:ptr没有初始化就进行了解引用,ptr是野指针。

到此,上卷结束,祝阅读愉快^_^

标签:动态内存,void,笔试,C语言,char,地址,str,GetMemory,指针
From: https://blog.csdn.net/2301_82135086/article/details/139352207

相关文章

  • Python面试宝典:Python中与设计模式相关的面试笔试题(1000加面试笔试题助你轻松捕获大厂
    Python面试宝典:1000加python面试题助你轻松捕获大厂Offer【第二部分:Python高级特性:第二十二章:代码设计和设计模式:第二节:设计模式】第二十二章:代码设计和设计模式第二节:设计模式创建型模式结构型模式行为型模式python中与设计模式相关的面试笔试题面试题1面试......
  • Python面试宝典:Python中与数据处理与清洗相关的面试笔试题(1000加面试笔试题助你轻松捕
    Python面试宝典:1000加python面试题助你轻松捕获大厂Offer【第二部分:Python高级特性:第二十六章:Python与数据科学:第二节:数据处理与清洗】第二十六章:Python与数据科学第二节:数据处理与清洗1.数据处理工具1.1Pandas1.2NumPy2.数据清洗工具2.1处理缺失......
  • C语言详解(动态内存管理)1
    Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎~~......
  • 解决C语言中scanf函数无法输入直接跳过的问题
    如果比较急的话,可以直接用这些方法,不急的话,建议读完。方法:1、看在调用该scanf函数前有没有用键盘输入过数据,有的话,可以尝试在该scanf函数前加个getchar();吃掉'\n'。2、在scanf前加一句"rewind(stdin);"(双引号里面的语句,不要把双引号也复制或打上去了),或者"fflush(stdin);",后......
  • C语言-----数组
    简单了解数组的知识以及数组的运用一、数组的概念二、一维数组1. 一维数组的创建与初始化2. 一维数组的使用三、二维数组1. 二维数组的创建与初始化2. 二维数组的使用四、用sizeof计算数组元素的个数一、数组的概念    数组可以说是目前为止学到的第......
  • c语言基础问题:1瓶汽水1元,2个空瓶可以换一瓶汽水,20元可以喝多少瓶汽水?
    老师布置的小组作业,好像是曾经的蓝桥杯题目,自己琢磨一下写出来了,写都写了还都加了注释,就想着不如发到这上面了,有遇到类似问题的可以看看,代码比较笨但是很好理解。#include<stdio.h>intmain()//1瓶汽水1元,2个空瓶可以换一瓶汽水,20元可以喝多少瓶汽水?{ intyuan=20;//本金......
  • 最大公约数(gcd())和最小公倍数(lcm())的c语言和c++详细解法
    最大公约数(gcd())和最小公倍数(lcm())最大公约数:定义:两个或多个整数共有的约数中最大的一个。例如:整数12和18,他们的公约数有1、2、3、6,其中最大的公约数是6。c语言解法:辗转相除法和更相减损法1、辗转相除法:思路:先求解较大的数除以较小的数的余数,再用较小的数除以前......
  • Linux下的C语言编程(指针)
    目录1一级指针1.1定义1.2指针的内存大小1.3指针的偏移1.3.1加法偏移1.3.2减法偏移2二级指针2.1定义2.2如何理解二级指针3三级指针3.1定义4数组和指针的关系4.1一维数组与指针的关系4.1.1数组名的性质4.1.2数组名与指针的地址偏移4.2二维数组与......
  • C语言:(动态内存管理)
    目录动态内存有什么用呢malloc函数开辟失败示范free函数calloc函数realloc函数当然realooc也可以开辟空间常⻅的动态内存的错误 对NULL指针的解引⽤操作对动态内存开辟的空间越界访问对⾮动态开辟内存使⽤free释放使⽤free释放⼀块动态开辟内存的⼀部分对同一......
  • C语言入门分析
    C语言是一门面向过程的高级语言,既有接近底层的特性,也有高级的语法。所谓C生万物,C语言自1972年诞生以来,一直都被业界所认可。而其它的一些曾经流行的编程语言如:COBOL、Pascal、Fortran等已经被基本不被使用了。但C语言却历久弥新,到现在为止,C语言在编程界的地位还是如此重要。那......