分享

悲催的程序猿,被STM32F4的内存对齐整整调试2天啊!!!!

 guitarhua 2018-01-18
目前在用stm32f407做项目,其中用到MODBUS,就自己写了一个。被内存对齐和结构指针调戏了两天,哎,满满都是泪啊。
现在写出来与大伙共同学习共同提高。
定义一个数组        
u16                PLC_Reg_4X[256] = {0};        //保持寄存器

在定义一个结构体
typedef        struct {
        float rated_frequency;
        float rated_voltage;
        float rated_current;
        float rated_power_factor;
}rated_typedef;

然后定义一个rated_typedef的指针指向PLC_Reg_4X数组
rated_typedef *ratedGG=(rated_typedef *)&PLC_Reg_4X[52];,指向数组的偶数元素。
此时通过 ratedGG->rated_frequency=50.0;这样操作来修改数组里面的数据,非常的方便。其他类型都是这样通过结构体指针来修改不同类型的变量。

然后,,,然后,,然后,,手贱了,将数组改为        u16                PLC_Reg_4X[255] = {0};         没错,就是修改了一下数组的大小。我觉得MODBUS我用不到这么多地址呀。
结果,F4有FPU,程序只要一运行对这个PLC_Reg_4X数组ratedGG->rated_frequency这样结构指针的浮点数的读写操作,单片机就直接App_Fault_ISR了,谷歌了一天,百思不得其解。

后来发现,将结构指针改为rated_typedef *ratedGG=(rated_typedef *)&PLC_Reg_4X[53];就是指向数组的奇数元素!就可以正常的运行了!!!好诡异啊,亲, 你知道是为什么吗?
这段代码之前都是好好的啊,为什么突然从偶数变成了奇数。程序中定义了好多这样的指针,一个个修改那是要我命啊。 。继续谷歌。。。。。。还是不得其解

是编译器的故障码?是单片机的BUG吗? 。

后再静下心来分析,偶数变奇数,肯定是数据对其出错了,难道编译器没帮我把我定义的数组进行对齐?
继续折腾,终于,无意中看到这个255,是奇数。回想之前是256是偶数,不会是这里出问题吧。。。。。。
马上修改编译,下载,运行。看到LED灯正常闪烁的瞬间,泪流满面啊~~~~~~。

原来编译器是从数组的最后一个元素开始对齐啊~啊~啊~。


就我目前所知,C语言标准中,没有规定数据访问的对齐要求,也没规定数据类型和内存地址的关系。即,任意类型的数据可以从任意的内存地址开始获得,至于需要从1个还是连续的多个内存地址获得该类型的数据,是由实现定义的。
但是,在处理器实现中,对数据访问的对齐是有要求的。X86等往往支持非对齐访问,但是非对齐效率低;RISC一般不支持非对齐访问或者作为一个特殊功能需要额外开启,非对齐一般导致异常。

编译器为了提高代码效率,默认数据在内存中的布局满足自对齐要求,即对8Bit一个内存地址的典型内存布局下,是多少Bit的变量就要多少Bit地址对齐。当然,结构体的情况更麻烦一些。。。

LZ位置,数组类型uint16_t,由于是16Bit变量,因此,数组首元素地址在16Bit边界就满足对齐要求;究竟是否与32Bit的边界对齐,没任何保证,除非你用align的扩展说明你有独特的对齐需求。
定义的结构体,都是FP32,所以默认32Bit对齐。处理器操作的时候,默认数据是32Bit对齐,否则会报错。

问题来了,16Bit对齐,但不对齐32Bit边界,除非支持非对齐访问,肯定会出错;16Bit恰好对齐到32Bit边界,没问题。任何变量或者内部内存的使用都可能导致内存布局的变化,进而影响uint16数组的对齐状况。

解决方案,
- 常见的方法是用align把uint16_t的起始地址约束成32Bit对齐的(原来是16),这样只要后来访问取地址都是偶数元素,那么自然满足32Bit对齐。这是效率最高的方法。
- 少用的方法或者在某些协议栈处理的方法,是对struct加约束,比如pack,align之类(当然,这个除了约束对齐,还有结构体Padding),告诉编译器这个结构体可能会从16Bit对齐或任意字节开始,完全没对齐。编译器会自动生成对应的读取代码,由于非对齐的可能性,数据读写的效率会非常低。比如32Bit在任意字节对齐下就是四个读字节操作,然后移位,加。这种方法可以近似认为是最安全的,移植性最好;就是慢。

我以前也遇到这样的问题.. 见帖子 http://www./thread-5506453-1-1.html

后来的的解决办法就是先用一个u32的临时变量tmp32读出来, 再赋值到浮点里去, 因为LDR指令不要求地址对齐
如果函数里临时变量少的话, tmp32就会分配到通用寄存器里, 编译器就会操作成: 内存--vldr-->通用寄存器--vmov-->浮点寄存器  , 相对于原来的  内存--vldr-->浮点寄存器  要多一个机器周期, 效率损失不多

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多