《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

相关文章

gradle第三方Jar包依赖统一管理方式

《gradle第三方Jar包依赖统一管理方式》:本文主要介绍gradle第三方Jar包依赖统一管理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录背景实现1.顶层模块build.gradle添加依赖管理插件2.顶层模块build.gradle添加所有管理依赖包

解决IDEA报错:编码GBK的不可映射字符问题

《解决IDEA报错:编码GBK的不可映射字符问题》:本文主要介绍解决IDEA报错:编码GBK的不可映射字符问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录IDEA报错:编码GBK的不可映射字符终端软件问题描述原因分析解决方案方法1:将命令改为方法2:右下jav

基于Python打造一个智能单词管理神器

《基于Python打造一个智能单词管理神器》这篇文章主要为大家详细介绍了如何使用Python打造一个智能单词管理神器,从查询到导出的一站式解决,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 项目概述:为什么需要这个工具2. 环境搭建与快速入门2.1 环境要求2.2 首次运行配置3. 核心功能使用指

MyBatis模糊查询报错:ParserException: not supported.pos 问题解决

《MyBatis模糊查询报错:ParserException:notsupported.pos问题解决》本文主要介绍了MyBatis模糊查询报错:ParserException:notsuppo... 目录问题描述问题根源错误SQL解析逻辑深层原因分析三种解决方案方案一:使用CONCAT函数(推荐)方案二:

Redis 热 key 和大 key 问题小结

《Redis热key和大key问题小结》:本文主要介绍Redis热key和大key问题小结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、什么是 Redis 热 key?热 key(Hot Key)定义: 热 key 常见表现:热 key 的风险:二、

IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤及问题解决

《IntelliJIDEA中配置SpringMVC环境的详细步骤及问题解决》:本文主要介绍IntelliJIDEA中配置SpringMVC环境的详细步骤及问题解决,本文分步骤结合实例给大... 目录步骤 1:创建 Maven Web 项目步骤 2:添加 Spring MVC 依赖1、保存后执行2、将新的依赖

Spring 中的循环引用问题解决方法

《Spring中的循环引用问题解决方法》:本文主要介绍Spring中的循环引用问题解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录什么是循环引用?循环依赖三级缓存解决循环依赖二级缓存三级缓存本章来聊聊Spring 中的循环引用问题该如何解决。这里聊

Spring Boot中JSON数值溢出问题从报错到优雅解决办法

《SpringBoot中JSON数值溢出问题从报错到优雅解决办法》:本文主要介绍SpringBoot中JSON数值溢出问题从报错到优雅的解决办法,通过修改字段类型为Long、添加全局异常处理和... 目录一、问题背景:为什么我的接口突然报错了?二、为什么会发生这个错误?1. Java 数据类型的“容量”限制

C语言中位操作的实际应用举例

《C语言中位操作的实际应用举例》:本文主要介绍C语言中位操作的实际应用,总结了位操作的使用场景,并指出了需要注意的问题,如可读性、平台依赖性和溢出风险,文中通过代码介绍的非常详细,需要的朋友可以参... 目录1. 嵌入式系统与硬件寄存器操作2. 网络协议解析3. 图像处理与颜色编码4. 高效处理布尔标志集合

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服