C语言之不小心踩了指针和内存地址对齐的坑

C语言之不小心踩了指针和内存地址对齐的坑 技术践行者 2023-04-19 16:32:42 1907

前言

指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量。

本质还是一个变量,指针提供了一种对存储位置的动态访问手段,(相对于普通变量而言,普通变量只能访问自己所占的存储位置)

内存地址对齐,是计算机在内存中的数据排列、访问数据的方式,包含了基本数据对齐和结构体数据对齐的两种相互独立又相互关联的部分。

现代计算机在内存中读写数据是按字节块进行操作,理论上任意类型的变量访问可以从任何地址开始,但是计算机系统对任意数据类型在内存中存放位置有限,它会要求这些数据的首地址的值为K(4位或者8位)的整数倍。

如何踩坑的?

在一份十分优秀的代码中,指针的使用率占比很高,因为指针能让代码实现变得更自由、更高效和更方便等诸多优点,可对于不十分熟悉指针的朋友来说,用起来也许就是灾难(常见的就是程序跑飞)

因此,通过指针的使用率大概就能判断一个人的编程能力水平

请看下面的代码,运行结果是怎么样的呢?

// 假设数组首地址为 0x00004000,符合内存对齐:4的倍数
static unsigned char sg_arrBuf[100]; 

int main()
{
    memset(sg_arrBuf, 0, sizeof(sg_arrBuf));

    // 地址为 0x00004000
    uint8_t *pucVal = (uint8_t *)&sg_arrBuf[0];

    // 地址为 0x00004001
    uint16_t *puiVal = (uint16_t *)&sg_arrBuf[1];

    *pucVal = 20;   // HEX: 0x14
    *puiVal = 2000; // HEX: 0x07d0

    printf("HEX: ");

    for (int i = 0; i < 3; i++)
    {
        printf("%02x ", sg_arrBuf[i]);
    }

    printf("\n");

    return 0;
}

很多朋友期望的结果如下(小端模式):

HEX: 14 d0 07

事实真的一定如此吗?

不一定!

也许部分朋友在自己电脑上打开 VS 复制粘贴运行了,编译后运行,结果还真和上面一样!!!你这不是在忽悠人吗!!!

那有试过在 MCU 上跑过吗?是不是程序跑飞了?

为什么?

当计算机读取或写入内存地址时,它将以字(word)大小的块进行存储。数据对齐意味着将数据放在等于字长的倍数的内存偏移处,正是由于这种CPU处理内存的方式从而提高了系统的性能。大多数CPU只能访问内存对齐的地址。

意味着部分系统架构体系对于未对齐的地址进行访问时会发生异常,如果尝试去访问内存未对齐的变量进行操作会导致总线错误。它只支持对齐访问。
比如我们访问一个 4 字节 (Double Word) 型的变量时,如果这个变量的起始地址是能被 4 整除的话,我们说这种访问是双字节对齐的。如果访问一个 2 字节 ( Word ) 变量,当起始地址能被 2 整除时是对齐的。访问字节 ( Byte ) 型变量,总是对齐的。

根据这个就能很快锁定问题原因了,那就是程序运行到这个位置就导致总线错误,从而跑飞了。

// 地址为 0x00004001,未对齐
*puiVal = 2000; // HEX: 0x07d0

预防及解决措施

关于上述的写法,有些朋友可能在电脑端实现了某个功能,电脑测试没有任何问题,但是一旦移植到 MCU 上就不行,那么抓紧检查一下是不是这个问题呢。
因此,为了确保我们的代码有很高的移植性和稳定性,那么一定要预防这种情况,可以通过采用访问字节 ( Byte ) 型变量,也可以使用memcpy的方式处理这种操作就能避免这种问题。

// 假设数组首地址为 0x00004000,符合内存对齐: 4的倍数
static unsigned char sg_arrBuf[100]; 

int main()
{
    memset(sg_arrBuf, 0, sizeof(sg_arrBuf));

    uint8_t ucVal = 20;
    uint16_t uiVal = 2000;

    memcpy(&sg_arrBuf[0], &ucVal, 1);
    memcpy(&sg_arrBuf[1], &uiVal, 2);

    printf("HEX: ");

    for (int i = 0; i < 3; i++)
    {
        printf("%02x ", sg_arrBuf[i]);
    }

    printf("\n");

    return 0;
}
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
技术践行者
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区