《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

相关文章

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

解决pandas无法读取csv文件数据的问题

《解决pandas无法读取csv文件数据的问题》本文讲述作者用Pandas读取CSV文件时因参数设置不当导致数据错位,通过调整delimiter和on_bad_lines参数最终解决问题,并强调正确参... 目录一、前言二、问题复现1. 问题2. 通过 on_bad_lines=‘warn’ 跳过异常数据3

解决RocketMQ的幂等性问题

《解决RocketMQ的幂等性问题》重复消费因调用链路长、消息发送超时或消费者故障导致,通过生产者消息查询、Redis缓存及消费者唯一主键可以确保幂等性,避免重复处理,本文主要介绍了解决RocketM... 目录造成重复消费的原因解决方法生产者端消费者端代码实现造成重复消费的原因当系统的调用链路比较长的时

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件

kkFileView启动报错:报错2003端口占用的问题及解决

《kkFileView启动报错:报错2003端口占用的问题及解决》kkFileView启动报错因office组件2003端口未关闭,解决:查杀占用端口的进程,终止Java进程,使用shutdown.s... 目录原因解决总结kkFileViewjavascript启动报错启动office组件失败,请检查of

在macOS上安装jenv管理JDK版本的详细步骤

《在macOS上安装jenv管理JDK版本的详细步骤》jEnv是一个命令行工具,正如它的官网所宣称的那样,它是来让你忘记怎么配置JAVA_HOME环境变量的神队友,:本文主要介绍在macOS上安装... 目录前言安装 jenv添加 JDK 版本到 jenv切换 JDK 版本总结前言China编程在开发 Java

C语言进阶(预处理命令详解)

《C语言进阶(预处理命令详解)》文章讲解了宏定义规范、头文件包含方式及条件编译应用,强调带参宏需加括号避免计算错误,头文件应声明函数原型以便主函数调用,条件编译通过宏定义控制代码编译,适用于测试与模块... 目录1.宏定义1.1不带参宏1.2带参宏2.头文件的包含2.1头文件中的内容2.2工程结构3.条件编