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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 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;
}


相关文章
|
1天前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
1天前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
1天前
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
4天前
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
3月前
|
消息中间件 存储 安全
|
4月前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
646 12
|
4月前
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
653 69
|
4月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
93 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
4月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
112 11
|
4月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
114 2