C语言---形参所导致的段错误

简介: C语言---形参所导致的段错误

前言

今天刷B站,无意之间看到一个宣称90%人都会错的嵌入式面试题。感兴趣就看了一下。卡了十多分钟才想明白,只是一个小知识点,但还是分享一下。


题目

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


(1)我们看,上面这串代码有什么问题?其实是一个很简单的知识点,但是很容易让人卡住。

(2)如果想不出这串代码的问题,就直接运行试试。

(3)从运行结果上来看,进行的段错误报错。那么我们就需要知道段错误产生的可能原因:

<1>访问未分配的内存:当程序试图访问未经分配的内存区域时,例如使用未初始化的指针或指针越界访问数组,就会导致段错误。

<2>访问空指针:当程序试图访问一个空指针,即指向空地址的指针,而没有进行有效的空指针检查时,会导致段错误。

<3>内存越界访问:当程序试图访问超过数组边界范围的内存位置时,即访问了数组之外的内存,也会导致段错误。

<4>野指针:当程序使用已经释放的内存或已失效的指针时,就会产生野指针,进而导致段错误。

<5>内存对齐错误:某些体系结构要求访问特定数据类型的内存地址必须按照一定的对齐方式进行,如果违反了对齐要求,就会导致段错误。

<6>栈溢出:当程序递归调用层级过深或者使用过多的局部变量导致栈空间耗尽时,也可能引发段错误。

<7>其他异常情况:例如访问只读内存、在信号处理程序中发生错误等也可能导致段错误。


问题定位

(1)我们知道了段错误产生的可能性之后,开始定位可能的原因。

<1>访问未分配的内存,程序试图访问未经分配的内存区域:这个是存在可能的,因为getmemory()函数中,malloc函数可能没有分配区域。

<2>访问空指针:因为一开始初始化str为空指针,getmemory()函数中malloc函数可能没有返回给str。

<3>内存越界访问:malloc申请的是100个字节数据,而“hello world”一共才12个字节数据(注意,字符串末尾有‘\0’)。所以这个可能性比较小。

<4>野指针:这里虽然申请了内存,但是没有释放,所以可能性也比较小。

<5>内存对齐:这串代码没有内存对齐的内容。所以可能性也很小。

<6>栈溢出:这里才申请100字节的数据,溢出可能性比较小。

<7>访问只读内存什么的,这里也没有出现,所以可能性比较小。

(2)总结来看,有可能是malloc没有分配到内存导致的段错误,也有可能是malloc申请到了内存,但是没有将返回的数据传递给str。


判断malloc是否申请成功内存

(1)我们要判断malloc是否申请成功内存,于是我就打算将strcpy()函数放在getmemory()中即可。

(2)发现可以成功运行,所以malloc内存申请成功了。


判断是否是访问空指针的问题

(1)如果str为空指针,那么就会出现段错误,所以我就在getmemory()函数后面加上打印str中存放地址的参数。

(2)运行结果来看,发现str果然是空指针。


问题分析

(1)现在我们知道了,问题出现在str是一个空指针。那么为什么str会是一个空指针呢?

(2)这个就需要涉及到到函数的传参过程了。我们都知道,函数中传入的参数是一个形参。而形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。

(3)那么这就面临一个问题,malloc申请到了一个内存区域,这个区域首地址假设为0x3fff。返回给p之后,因为p是形参,所以函数结束之后,被释放,0x3fff这个值并没有传递到str中。

(4)下面是图解


如何更改

(1)知道形参的工作原理之后,就很好更改了。我们在传参的过程,可以使用二级指针。图解如下

(2)改为二级指针之后,我们还需要知道,每一次malloc申请内存,都要记得即使释放内存。所以我还增加了一个freememory()函数。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void getmemory(char **p)
{
  *p = (char*)malloc(100);
}
void freememory(char **p)
{
  free(*p);
  *p = NULL;
}
void test(void)
{
  char *str = NULL;
  getmemory(&str);
  strcpy(str,"hello world");
  printf("%s\n",str);
  freememory(&str);
}
int main()
{
  test();
  return 0;
}
目录
相关文章
|
NoSQL 编译器 程序员
【C语言】 --- 段错误
【C语言】 --- 段错误
239 0
|
2月前
|
C语言
C语言形参和实参的区别
在C语言中,形参(形式参数)与实参(实际参数)有着明确的角色区分。形参是在函数定义中声明的参数,用于接收调用函数时传入的数据;实参则是调用函数时传递的具体值或变量。简言之,实参提供数据,形参接收并处理这些数据。
|
7月前
|
存储 程序员 C语言
18.C语言:指针数组作main函数的形参示例
18.C语言:指针数组作main函数的形参示例
83 0
|
7月前
|
C语言
【C语言】函数实参与形参详解
【C语言】函数实参与形参详解
291 0
|
7月前
|
C语言
C语言中形参列表为指针的三种不同swap函数的通俗理解
C语言中形参列表为指针的三种不同swap函数的通俗理解
56 0
|
7月前
|
存储 编译器 程序员
【新手解答1】深入探索 C 语言:变量名、形参 + 主调函数、被调函数 + 类和对象 + 源文件(.c 文件)、头文件(.h 文件)+ 库
【新手解答1】深入探索 C 语言:变量名、形参 + 主调函数、被调函数 + 类和对象 + 源文件(.c 文件)、头文件(.h 文件)+ 库
148 0
|
C语言
【C 语言】文件操作 ( 配置文件读写 | 读取配置文件 | 函数接口形参 | 读取配置文件的逐行遍历操作 | 读取一行文本 | 查找字符 | 删除字符串前后空格 )
【C 语言】文件操作 ( 配置文件读写 | 读取配置文件 | 函数接口形参 | 读取配置文件的逐行遍历操作 | 读取一行文本 | 查找字符 | 删除字符串前后空格 )
174 0
|
C语言
【C语言函数参数详解】——实际参数(实参)&形式参数(形参)
【C语言函数参数详解】——实际参数(实参)&形式参数(形参)
444 0
|
算法 C语言
13【C语言 & 趣味算法】分糖果 问题。(数组名作为函数形参,亦即:形参数组名作 指针变量)
13【C语言 & 趣味算法】分糖果 问题。(数组名作为函数形参,亦即:形参数组名作 指针变量)
13【C语言 & 趣味算法】分糖果 问题。(数组名作为函数形参,亦即:形参数组名作 指针变量)