加个变量,程序崩了

前述

大家在平常的编程过程应该会碰到各种奇葩的问题吧,反正我最近是碰到了一次,再此跟大家分享一下。事情的原因是我在程序中增加了一个变量,然后就会导致程序每次都会进入异常。

示例代码

我将代码简化了,使用两个模块来演示这个问题。第一个模块是Dev模块。

下面是Dev模块的头文件,Dev结构体中有一个数组。

#include <stdio.h>
#include <string.h>

typedef struct {
 int a_[100];
 char b_;
}Dev;

void DevInit(Dev *c_this);

下面是Dev模块的源文件代码,里面只有一个memset。

#include "Dev.h"

void DevInit(Dev *c_this) {  
 memset(c_this, 0, sizeof(Dev));
}

第二个模块是DevManager,相关数据结构如下:

#include "Dev.h"

#pragma pack(1)

typedef struct {
 Dev uart_;
 char num_; // Adding this variable causes a crash
 Dev iic_;
}DevManage;

#pragma pack()

DevManage dev_manage;

void DevManagerInit(void) {
 DevManage *c_this = &dev_manage;
 
 memset(c_this, 0, sizeof(DevManage));
 
 DevInit(&c_this->uart_);
 DevInit(&c_this->iic_);
}

DevManage结构体包含uart以及iic设备,以及我新加入的一个num_变量,由于新增了num_变量以及与之相关的业务会导致每次调试目标板都会进入异常。

尝试解决异常问题

根据调试情况看,每次都会出现异常,说明是个小问题。就怕偶尔出现异常,不容易复现。

思路应该非常清晰,出现异常时候查看LR寄存器的值,LR寄存器主要有两个功能。

  1. 保存子程序返回地址。使用BL或BLX时,跳转指令自动把返回地址放入r14中

  2. 当异常发生时,异常模式的R14用来保存异常返回地址

根据LR寄存器的值,从而定位到是执行DevInit(&c_this->iic_)函数中的memset导致的。

第一反应是DevInit中传入的对象可能为空,操作了非法内存才导致的错误。于是又重新调试了一遍,发现DevInit中对象的地址并不为空,而且就是等于实体对象中设备的地址。

这一刻我陷入了深深的自我怀疑,memset难道不是这样用的?难道不是传入一个地址,清0,然后sizeof(DevManage)就完事了?

我这代码怎么会出错,memset就是这样用的,天王老子来我也是对的,这样的心理是不是也深度还原了碰到问题时的你们。

如果是你,该如何继续...

救命稻草

有人说,汇编是最后的救命稻草。那我也尝试抓住这根稻草,出现问题时如下图:

问题主要出现红色箭头指向的这一行,经过查询资料得知_aeabi_memclr4是一个用于ARM嵌入式系统的函数,用于将内存区域清零。函数名中的"_a"表示该函数符合ARM嵌入式应用二进制接口(Embedded Application Binary Interface,EABI)规范。在调用_aeabi_memclr4时,需要确保传入的内存地址是四字节对齐的。再看图片中的对象地址为0x200001BD,刚好比四字节对齐地址0x200001BC多了一个字节。

再回头看DevManage对象,这里使用了伪指令#pragma pack(1)让内存分配进行单字节对齐。因为Dev对象是按照四字节对齐的,紧接着引入了新的成员num_占用一个字节。所以导致iic_对象的地址相对于未增加变量之前的地址偏移了一个字节,导致不是四字节对齐的了,从而引发了错误。

就示例中的代码而言,只需要把强制DevManage对象单字节对齐的功能删除即可解决问题,因为默认情况下是四字节对齐的。

成员分配

由于#pragma pack(1)具有作用域限制,这里其实只是对num_变量做了单字节对齐的限制,而并没有对Dev对象的内存分配起到限制作用,Dev对象仍然是按照4字节对齐的(默认值),所以Dev对象的所占用的长度一定是101*4=404字节。而整个DevManager对象的大小就101 * 4 + 1 + 101 * 4 = 809字节,成员分配如下图所示。

最后

到最后在抛出一个问题,对于上述的代码在vscode中使用gcc编译执行为何没有问题?

原文链接:加个变量,程序崩了

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值