《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

相关文章

Windows环境下解决Matplotlib中文字体显示问题的详细教程

《Windows环境下解决Matplotlib中文字体显示问题的详细教程》本文详细介绍了在Windows下解决Matplotlib中文显示问题的方法,包括安装字体、更新缓存、配置文件设置及编码調整,并... 目录引言问题分析解决方案详解1. 检查系统已安装字体2. 手动添加中文字体(以SimHei为例)步骤

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

MySql基本查询之表的增删查改+聚合函数案例详解

《MySql基本查询之表的增删查改+聚合函数案例详解》本文详解SQL的CURD操作INSERT用于数据插入(单行/多行及冲突处理),SELECT实现数据检索(列选择、条件过滤、排序分页),UPDATE... 目录一、Create1.1 单行数据 + 全列插入1.2 多行数据 + 指定列插入1.3 插入否则更

SpringSecurity整合redission序列化问题小结(最新整理)

《SpringSecurity整合redission序列化问题小结(最新整理)》文章详解SpringSecurity整合Redisson时的序列化问题,指出需排除官方Jackson依赖,通过自定义反序... 目录1. 前言2. Redission配置2.1 RedissonProperties2.2 Red

nginx 负载均衡配置及如何解决重复登录问题

《nginx负载均衡配置及如何解决重复登录问题》文章详解Nginx源码安装与Docker部署,介绍四层/七层代理区别及负载均衡策略,通过ip_hash解决重复登录问题,对nginx负载均衡配置及如何... 目录一:源码安装:1.配置编译参数2.编译3.编译安装 二,四层代理和七层代理区别1.二者混合使用举例

PostgreSQL中rank()窗口函数实用指南与示例

《PostgreSQL中rank()窗口函数实用指南与示例》在数据分析和数据库管理中,经常需要对数据进行排名操作,PostgreSQL提供了强大的窗口函数rank(),可以方便地对结果集中的行进行排名... 目录一、rank()函数简介二、基础示例:部门内员工薪资排名示例数据排名查询三、高级应用示例1. 每

全面掌握 SQL 中的 DATEDIFF函数及用法最佳实践

《全面掌握SQL中的DATEDIFF函数及用法最佳实践》本文解析DATEDIFF在不同数据库中的差异,强调其边界计算原理,探讨应用场景及陷阱,推荐根据需求选择TIMESTAMPDIFF或inte... 目录1. 核心概念:DATEDIFF 究竟在计算什么?2. 主流数据库中的 DATEDIFF 实现2.1

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控