来源于《高质量C/C++编程》的几道经典面试题

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: .str传给p的时候,p是str的临时拷贝,有自己的独立空间,当GetMemory函数内部申请了空间后,地址放在p中,str仍然是NULL。当Getmemory函数返回之后,strcpy拷贝的时候,形成了非法访问。

本文介绍几个非常经典的笔试题,原题+详细解析,供参考


题目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;
}


请问运行Test 函数会有什么样的结果?


结果:


01dc9231d8644c769e84e725668e08dd.png


解析:


void Getmemory(char* p)//用p来接收str的数据,p是形参,形参是实参的一份临时拷贝
{
  p = (char*)malloc(100);//给p开辟一个100字节的空间。
  //还有这里开辟了空间后,并没有释放,造成内存泄漏。
}
void test(void)
{
  char* str = NULL;
  Getmemory(str);//将str这个指针传过去,相当于传值过去,并不是传址输入,对形参的改变并不会影响实参。
  strcpy(str, "hello world");//这里的str还是空指针,并没有空间给它拷贝,形成NULL非法访问,这里就出错了。
  printf(str);
}
int main()
{
  test();
  return 0;
}


1.str传给p的时候,p是str的临时拷贝,有自己的独立空间,当GetMemory函数内部申请了空间后,地址放在p中,str仍然是NULL。当Getmemory函数返回之后,strcpy拷贝的时候,形成了非法访问。


2.在Getmemory函数内部,动态申请空间,但是没有释放,造成内存泄漏


正确做法1:


void Getmemory(char** p)//str是一级指针,&str就是二级指针
{
  *p = (char*)malloc(100);//*p就相当于str,就是给str开辟一个100字节的空间
}
void test(void)
{
  char* str = NULL;
  Getmemory(&str);//这里进行传址输入,利用形参修改实参
  strcpy(str, "hello world");//这里的str就拥有了100字节的空间了
  printf(str);
  free(str);//释放空间
  str=NULL//手动置NULL
}
int main()
{
  test();
  return 0;
}


正确做法2:


char* Getmemory(char* p)//那Getmemory函数返回值就要改成char *类型接收指针
{
  p = (char*)malloc(100);
  return p;//将p返回
}
void test(void)
{
  char* str = NULL;
  str=Getmemory(str);//这里用指针str来接收Getmemory函数内部形参开辟的空间
  strcpy(str, "hello world");
  printf(str);
  free(str);//释放空间
  str=NULL//手动置NULL
}
int main()
{
  test();
  return 0;
}

9bda877b8b95419c9962d543f9e05202.png


题目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;
}


请问运行Test 函数会有什么样的结果?


结果:


06a72b0e201c4a2a846153b2d06faccd.png


解析:


char* Getmemory(void)
{
  char p[] = "hello world";//创建一个数组p,里面存放着字符串
  return p;//返回数组名,也就是返回了数组首元素的地址
}
void test(void)
{
  char* str = NULL;
  str = Getmemory();//Getmemory函数的返回值用str来接收
  //数组p的首元素地址用str来接收
  //但要注意的是,p数组是在Getmemory内部创建,创建在栈区,出了函数,这个空间就要返回给操作系统,不再受指针p操控。
  //所以str虽然得到了数组的首元素地址,但这片空间已经不再属于数组空间了,所以形成了非法访问
  printf(str);//打印str指向的字符
}
int main()
{
  test();
  return 0;
}


这个问题统称为返回栈空间地址问题


就是在栈上开辟的空间,并将指向这块空间的地址返回,但这块空间的使用者发现改变,不再是原先的使用者了,而再根据地址找到这块空间来访问就会出问题。


这个要按自己的实际需求来正确修改。


题目3:内存泄漏


void Getmemory(char** p, int num)
{
  *p = (char*)malloc(num);
}
void test(void)
{
  char* str = NULL;
  Getmemory(&str, 100);
  strcpy(str, "xiao tao");
  printf(str);
}
int main()
{
  test();
  return 0;
}


请问运行Test 函数会有什么样的结果?


90d27fbb96524b95ab3f028bb2e13676.png


解析:


这个题目跟第一题的修改后的题目差不多


void Getmemory(char** p, int num)
{
  *p = (char*)malloc(num);//开辟100个字节空间给*p  *p就是str
  //所以给str开辟了100字节的空间
}
void test(void)
{
  char* str = NULL;
  Getmemory(&str, 100);//传址输入,可以通过对形参修改而修改实参
  strcpy(str, "xiao tao");//对str指向的空间进行拷贝
  printf(str);//打印拷贝后的空间内容
  //最最重要的一点是,空间申请后,释放呢?这里没有释放所以最后会造成内存泄漏
  free(str);//释放内存
  str = NULL;
}
int main()
{
  test();
  return 0;
}


题目4:非法访问


void test(void)
{
  char* str = (char*)malloc(100);
  strcpy(str, "xiao tao");
  free(str);
  if (str != NULL)
  {
    strcpy(str, "666");
    printf(str);
  }
}
int main()
{
  test();
  return 0;
}


请问运行Test 函数会有什么样的结果?


3bb82f1211cb4e2f87691eee5e911720.png


解析:


void test(void)
{
  char* str = (char*)malloc(100);//开辟100个字节的空间
  strcpy(str, "xiao tao");//给指向srt的空间进行拷贝
  free(str);//释放空间
  //free的功能是将原先指向这块空间的指针与这块空间之间的联系断开,这块空间不再首这个指针调控,回归到操作系统,供有需要的使用
  //而free释放空间后并不会将原先指向这块空间的指针置为空指针,还是指向原先的地方,所以下面的操作就进行下去了
  //所以这个指针又去访问那块空间让其被覆盖成666,这就造成非法访问了
  if (str != NULL)//如果str不为空指针进行下面操作
  {
    strcpy(str, "666");
    printf(str);//打印指向str那块空间的内容
  }
}
int main()
{
  test();
  return 0;
}


正确做法:在不改变原意的基础上修改:


void test(void)
{
  char* str = (char*)malloc(100);//开辟100个字节的空间
  strcpy(str, "xiao tao");//给指向srt的空间进行拷贝
  free(str);//释放空间
  str = NULL;//释放空间后手动置为NULL,让下面的操作无法进行
  if (str != NULL)//如果str不为空指针进行下面操作
  {
    strcpy(str, "666");
    printf(str);//打印指向str那块空间的内容
  }
}
int main()
{
  test();
  return 0;
}


相关文章
|
4月前
|
C++
C++ 语言异常处理实战:在编程潮流中坚守稳定,开启代码可靠之旅
【8月更文挑战第22天】C++的异常处理机制是确保程序稳定的关键特性。它允许程序在遇到错误时优雅地响应而非直接崩溃。通过`throw`抛出异常,并用`catch`捕获处理,可使程序控制流跳转至错误处理代码。例如,在进行除法运算或文件读取时,若发生除数为零或文件无法打开等错误,则可通过抛出异常并在调用处捕获来妥善处理这些情况。恰当使用异常处理能显著提升程序的健壮性和维护性。
84 2
|
2月前
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
393 67
|
4月前
|
算法 C语言 C++
C++语言学习指南:从新手到高手,一文带你领略系统编程的巅峰技艺!
【8月更文挑战第22天】C++由Bjarne Stroustrup于1985年创立,凭借卓越性能与灵活性,在系统编程、游戏开发等领域占据重要地位。它继承了C语言的高效性,并引入面向对象编程,使代码更模块化易管理。C++支持基本语法如变量声明与控制结构;通过`iostream`库实现输入输出;利用类与对象实现面向对象编程;提供模板增强代码复用性;具备异常处理机制确保程序健壮性;C++11引入现代化特性简化编程;标准模板库(STL)支持高效编程;多线程支持利用多核优势。虽然学习曲线陡峭,但掌握后可开启高性能编程大门。随着新标准如C++20的发展,C++持续演进,提供更多开发可能性。
92 0
|
6月前
|
编译器 C++ 开发者
C++一分钟之-C++20新特性:模块化编程
【6月更文挑战第27天】C++20引入模块化编程,缓解`#include`带来的编译时间长和头文件管理难题。模块由接口(`.cppm`)和实现(`.cpp`)组成,使用`import`导入。常见问题包括兼容性、设计不当、暴露私有细节和编译器支持。避免这些问题需分阶段迁移、合理设计、明确接口和关注编译器更新。示例展示了模块定义和使用,提升代码组织和维护性。随着编译器支持加强,模块化将成为C++标准的关键特性。
403 3
|
2月前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
313 10
|
1月前
|
消息中间件 存储 安全
|
2月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
66 2
|
3月前
|
存储 算法 C++
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
文章详细探讨了C++中的泛型编程与STL技术,重点讲解了如何使用模板来创建通用的函数和类,以及模板在提高代码复用性和灵活性方面的作用。
64 2
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
|
2月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
92 11
|
2月前
|
存储 编译器 C++
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
50 9