《C语言杂记》从getmemery()函数看内存管理、函数传参等一系列问题

2024-08-30 13:58

本文主要是介绍《C语言杂记》从getmemery()函数看内存管理、函数传参等一系列问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在C 面试题目中,会经常出现getmemery()函数的改错题,比如下面这道题,
例一:代码如下:

#include <stdio.h>  
char *getmemery()  
{  char p[] = "hello world!";  return p;  
}  
void main()  
{  char *str = NULL;  str = getmemery();  printf("%s\n",str);  
}  

这题主要考察的是我们对内存管理的了解;
咱们先执行一下,先不管编译时会出现什么错误,执行结果如下:
这里写图片描述
可以看到执行结果是一段乱码,而不是想象中的 hello world!
为什么会出现这种结果,在编译是就能看到,编译时出现了警告如下:
这里写图片描述
警告:函数返回局部变量的地址;函数返回局部变量的地址会产生什么后果呢。我们知道,局部变量存储在栈区,在代码块执行前申请一片内存,执行完毕后,这块内存即被释放;*getmemery()函数是个指针型函数,指针型函数返回的是一个指针,就是返回的是一个地址,但是指针型函数要注意的是,其返回的地址必须是函数调用结束后依然存在的存储单位地址;而此处p[]是局部变量,其返回的地址p在函数调用结束后已经不存在了,所以执行是会出现乱码!先看看如何更改会正确,代码如下:

#include <stdio.h>  
char *getmemery()  
{  char *p = "hello world!";  return p;  
}  
main()  
{  char *str = NULL;  str = getmemery();  printf("%s\n",str);  
}  

执行结果如下:
这里写图片描述
执行结果正确!
看看代码,只是将p[] = "hello world!"改成了*p = “hello world!”,结果却不同呢! 将字符串赋给数组和指针有什么区别呢?

一个字符串,如"hello world!",一般为字符串常量,既然是常量,存储在常量区,常量的生存周期是伴随着整个程序的,可以用它对字符指针赋值,或初始化,相当于把这个字符串常量的首地址赋给这个指针,正如上面代码中 char *p=“hello world!”;但是,当用"hello world!“给字符数组作初始化时,这里的"hello world!”,并非一个字符串常量,只是复制了一份放在数组里,而是相当于一个初始化列表{‘h’,‘e’,‘l’,‘l’,‘o’,’ ‘,‘w’,‘o’,‘r’,‘l’,‘d’,’\0’},在其他任何时候,他对表示一个字符串常量。而数组名也是一个指针常量,不能对常量赋值。所以char p[]="hello world!"正确,而char p[12]; p="hello world!"错误,p为指针常量,不能修改,当然也不能赋值!

回到刚才的两段代码,结果的差别便区别在上述论述中!当然,我们也可以这样改:

#include <stdio.h>  
char *getmemery()  
{  static char p[] = "hello world!";  return p;  
}  
void main()  
{  char *str = NULL;  str = getmemery();  printf("%s\n",str);  
}  

结果如下:
这里写图片描述
仍能得到正确结果!

static的作用在这里先不详解,但C语言面试中,经常会考察static的作用,static的作用简单说就两种:(1)限制变量的作用域;(2)限制变量的生存周期;
所以上述代码中用static 修饰p[],使p[]此时不是存储在栈区,而是存储在静态存储区,生存周期是整个程序的开始到结束!

例二:下面再给出一个getmemery()函数的改错题,代码如下:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>    
void getmemery(char *p)  
{  p = (char *)malloc(100);  
}  
void main()  
{  char *str = NULL;  getmemery(str);  strcpy(str,"hello world!");  printf("%s\n",str);  
}  

这题考察的是我们对函数传参的理解!
我们先对代码进行编译,并没有错误与警告,执行结果如下:
这里写图片描述
段错误 (核心已转存储),这个错误在前面的文章中提到过,现在再解释一下;一 般来说,段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越界访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了. 在编程中以下几类做法容易导致段错误,基本是是错误地使用指针引起的。

1)访问系统数据区,尤其是往系统保护的内存地址写数据最常见就是给一个指针以0地址;
2)内存越界(数组越界,变量类型不一致等);
3) 访问到不属于你的内存区域 。

我们先来解决问题,从上述描述中,问题还是出在错误的使用指针:
(1)访问系统数据区,尤其是往系统保护的内存地址写数据最常见就是给一个指针以0地址 ,这里并不是这个原因。
(2)内存越界(数组越界,变量类型不一致等)这里我们给其分配的大小是足够的。
(3)访问到不属于你的内存区域 。
问题出在这,上述代码传入getmemery(char *p)函数的字符串指针是形参,在函数内部修改形参并不能真正的改变传入形参的值,执行完char *str = NULL; gememory(str);后的str仍为NULL;

一般函数的传递都是值传递,不会改变函数外的变量值。简单地说,就是形参不能够改变实参,实参只是复制了一份给形参!其自身并没有被改变,所以str所指向的仍是一个未知区域,所以会出此上述错误。

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
void getmemery(char **p)  
{  *p = (char *)malloc(100);  
}  
void main()  
{  char *str = NULL;  getmemery(&str);  strcpy(str,"hello world!");  printf("%s\n",str);  
}  

执行结果如下:
$ gcc -o 1 1.c
$ ./1
hello world!
这就是我们常说的“地址传递”,将str的地址传给getmemery()函数,getmemery()函数就会通过地址修改str里面的值,这样就会得到正确的结果。所以,我们要记住函数传参的两种方式:1)值传递 2)地址传递。

这篇关于《C语言杂记》从getmemery()函数看内存管理、函数传参等一系列问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1121015

相关文章

mybatis和mybatis-plus设置值为null不起作用问题及解决

《mybatis和mybatis-plus设置值为null不起作用问题及解决》Mybatis-Plus的FieldStrategy主要用于控制新增、更新和查询时对空值的处理策略,通过配置不同的策略类型... 目录MyBATis-plusFieldStrategy作用FieldStrategy类型每种策略的作

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Python Jupyter Notebook导包报错问题及解决

《PythonJupyterNotebook导包报错问题及解决》在conda环境中安装包后,JupyterNotebook导入时出现ImportError,可能是由于包版本不对应或版本太高,解决方... 目录问题解决方法重新安装Jupyter NoteBook 更改Kernel总结问题在conda上安装了

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

pip install jupyterlab失败的原因问题及探索

《pipinstalljupyterlab失败的原因问题及探索》在学习Yolo模型时,尝试安装JupyterLab但遇到错误,错误提示缺少Rust和Cargo编译环境,因为pywinpty包需要它... 目录背景问题解决方案总结背景最近在学习Yolo模型,然后其中要下载jupyter(有点LSVmu像一个

解决jupyterLab打开后出现Config option `template_path`not recognized by `ExporterCollapsibleHeadings`问题

《解决jupyterLab打开后出现Configoption`template_path`notrecognizedby`ExporterCollapsibleHeadings`问题》在Ju... 目录jupyterLab打开后出现“templandroidate_path”相关问题这是 tensorflo

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

如何解决Pycharm编辑内容时有光标的问题

《如何解决Pycharm编辑内容时有光标的问题》文章介绍了如何在PyCharm中配置VimEmulator插件,包括检查插件是否已安装、下载插件以及安装IdeaVim插件的步骤... 目录Pycharm编辑内容时有光标1.如果Vim Emulator前面有对勾2.www.chinasem.cn如果tools工

Python itertools中accumulate函数用法及使用运用详细讲解

《Pythonitertools中accumulate函数用法及使用运用详细讲解》:本文主要介绍Python的itertools库中的accumulate函数,该函数可以计算累积和或通过指定函数... 目录1.1前言:1.2定义:1.3衍生用法:1.3Leetcode的实际运用:总结 1.1前言:本文将详