amobbs.com 阿莫电子技术论坛

标题: 悲催的程序猿,被STM32F4的内存对齐整整调试2天啊!!!! [打印本页]

作者: ahong2hao    时间: 2014-10-16 14:35
标题: 悲催的程序猿,被STM32F4的内存对齐整整调试2天啊!!!!
本帖最后由 ahong2hao 于 2014-10-16 14:35 编辑

目前在用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灯正常闪烁的瞬间,泪流满面啊~~~~~~。

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



作者: motoedy    时间: 2014-10-16 14:57
很好的经验,感谢分享。。
作者: icoyool    时间: 2014-10-16 15:01
关于对齐,是有伪指令的,偶数你能这样蒙,如果是16对齐,32对齐呢?
作者: icoyool    时间: 2014-10-16 15:06
#pragma   pack(32)
u32 Timer2Conuter ;
#pragma   pack(1)
UINT RandomN;
IAR 下的例子
作者: codeman    时间: 2014-10-16 15:55
关于对齐真没彻底搞明白过,不同编译器处理好像不同

为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
作者: weuser    时间: 2014-10-16 15:58
这好像根编译器和芯片都没关系啊。
float 32位长,rated_typedef 是 32*4=128位长,即16字节。
PLC_Reg_4X 元素定义成 16位,  PLC_Reg_4X 255个元素,总长 16*255,不是128位,即16字节的倍数怎么搞?

结构体元素都是 32位的 为什么要指向 16位元素的数组?
作者: tj7856    时间: 2014-10-16 15:59
路过,学习了
作者: 想不到    时间: 2014-10-16 19:31
学习学习
作者: canspider    时间: 2014-10-16 19:32
楼主学会看map文件就好了
作者: armstrong    时间: 2014-10-16 19:57
分析得出,楼主即便是到现在(你发了此贴以后)也还是没搞明白内存对齐的知识。其实对齐不是看你定义的数组长度是否偶数,而是看你的数组基本类型。
举例吧:
u8  array[n]; // 这是1字节对齐的,与n取值无关。
u16 array[n]; // 这是2字节对齐的,与n取值无关。
u32 array[n]; // 这是4字节对齐的,与n取值无关。
你把u16型数组的长度改为256可用,那仅仅是连接器的偶然,知识要严谨不能依赖偶然;既然整个数组的起始地址不能保证是4字节对齐,你在此数组的52索引处也同样不能保证4字节对齐。
而你定义的结构体必须4字节对齐,因为其成员是float类型。


作者: armstrong    时间: 2014-10-16 20:01
本帖最后由 armstrong 于 2014-10-16 20:03 编辑

keil mdk中,你要查看一下__align()的用法,这是解决你问题的关键字;
iar ewarm中,你查看一下#pragma data_alignment的用法,这是解决你的问题的关键;
楼主依据以上知识点去实验,了解对齐的知识;真理不怕检验。
作者: ts-fjw    时间: 2014-10-16 20:15
标记一下 明天看看
作者: myxiaonia    时间: 2014-10-16 20:15
armstrong 发表于 2014-10-16 20:01
keil mdk中,你要查看一下__align()的用法,这是解决你问题的关键字;
iar ewarm中,你查看一下#pragma dat ...

nice,讲解的很漂亮
作者: salahading    时间: 2014-10-16 20:16
一个问题挡你几天是常有的事,只能说搞技术的苦逼
作者: dcl_yufeimen    时间: 2014-10-16 20:24
编译器还有从数组最后一位对齐这种情况啊
作者: hhxb    时间: 2014-10-16 20:45
原来如此
作者: star_tale    时间: 2014-10-16 21:03
为啥不用结构体数组,用结构体操作数组为毛?
作者: dz46316740    时间: 2014-10-16 21:08
学习了。
作者: armstrong    时间: 2014-10-16 21:09
myxiaonia 发表于 2014-10-16 20:15
nice,讲解的很漂亮

呵呵,可识货的人并不多。
作者: myxiaonia    时间: 2014-10-16 22:04
armstrong 发表于 2014-10-16 21:09
呵呵,可识货的人并不多。

不知道lz明白了没
作者: 聆听风声    时间: 2014-10-16 23:18
学习了!
作者: tyou    时间: 2014-10-16 23:37
C的强制类型转换
作者: 3050311118    时间: 2014-10-16 23:41
用__packet紧凑型关键字
作者: nongxiaoming    时间: 2014-10-17 00:31
armstrong 发表于 2014-10-16 21:09
呵呵,可识货的人并不多。

每次都忍不住多看你头像几眼
作者: 太阳哥    时间: 2014-10-17 07:08
好贴      
作者: armstrong    时间: 2014-10-17 08:26
3050311118 发表于 2014-10-16 23:41
用__packet紧凑型关键字

能用对齐解决的问题就不要用pack,pack是很损失性能的东西。比如对齐情况下,4字节存储与加载只需1次总线访问,而放弃对齐的pack会导致2次总线访问,损失了性能。
作者: hzyjq    时间: 2014-10-17 08:28
Mark,学习了
作者: dr2001    时间: 2014-10-17 08:44
本帖最后由 dr2001 于 2014-10-17 08:54 编辑

就我目前所知,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在任意字节对齐下就是四个读字节操作,然后移位,加。这种方法可以近似认为是最安全的,移植性最好;就是慢。

作者: love0679    时间: 2014-10-17 08:47
经验之谈啊,mark,以后一定用得上
作者: 蓝蓝的恋    时间: 2014-10-17 08:48
经验记在本本上,留着后用~
作者: ahong2hao    时间: 2014-10-17 09:00
armstrong 发表于 2014-10-16 20:01
keil mdk中,你要查看一下__align()的用法,这是解决你问题的关键字;
iar ewarm中,你查看一下#pragma dat ...

感谢
作者: ahong2hao    时间: 2014-10-17 09:05
weuser 发表于 2014-10-16 15:58
这好像根编译器和芯片都没关系啊。
float 32位长,rated_typedef 是 32*4=128位长,即16字节。
PLC_Reg_4X ...

为了方便MODBUS的操作。

这样做的效果就是数组的下标就是MODBUS所对应的地址。增加或减少MODBUS寄存器的时候,只需要修改数组大小即可。


作者: XA144F    时间: 2014-10-17 09:10
只能说楼主的知识储备不过关,不怪编译器不行。
float型数据是4字节的,但你定义u16的数组,要对齐完全靠运气,如果换成u32,那就不会有对不齐的问题了。
stm32的密集存储节省了不少内存呢,arm9不管u8、u16还是u32,都是要占用4字节的空间,地址值是能被4整除的。
或者,你把结构体和你的数组用一个共用体包起来,就不担心对齐的问题了,这样保证数组的开始的地址是能被4整除的。
作者: Name_006    时间: 2014-10-17 09:17
armstrong 发表于 2014-10-17 08:26
能用对齐解决的问题就不要用pack,pack是很损失性能的东西。比如对齐情况下,4字节存储与加载只需1次总线 ...

额  大哥 换个头像呗  在公司 被别人看到不好  。。                                                                       
作者: hwie    时间: 2014-10-17 09:17

Mark,学习了
作者: echoecho_cxp    时间: 2014-10-17 09:18
我也被坑过。
所以现在从来不使用 奇数的数组大小和地址。
作者: ahong2hao    时间: 2014-10-17 09:23
XA144F 发表于 2014-10-17 09:10
只能说楼主的知识储备不过关,不怪编译器不行。
float型数据是4字节的,但你定义u16的数组,要对齐完全靠运 ...

惭愧啊,在未遇到这个问题的时候,我还真不知道内存对齐是干啥用的。
作者: wzavr    时间: 2014-10-17 09:53
armstrong 发表于 2014-10-16 19:57
分析得出,楼主即便是到现在(你发了此贴以后)也还是没搞明白内存对齐的知识。其实对齐不是看你定义的数组 ...

基础知识真扎实,
以前没网络的时候,能静下心把一本书从头到尾看几遍,透彻.
有网络后,从来只是翻一翻,有问题上网查,以为快了,起始想一想,现在学什么东西,都不扎实
作者: wx85105157    时间: 2014-10-17 13:34
armstrong 发表于 2014-10-16 21:09
呵呵,可识货的人并不多。

我们是识货的。说的好~
作者: qs6361036    时间: 2014-10-18 10:00
armstrong 发表于 2014-10-16 19:57
分析得出,楼主即便是到现在(你发了此贴以后)也还是没搞明白内存对齐的知识。其实对齐不是看你定义的数组 ...

说的很有道理 , 楼主的数据类型不一致
作者: Joezhu    时间: 2014-10-18 12:48
8位机没有数据对齐问题,16位32位机就要注意了
作者: bigallium    时间: 2014-10-18 15:34
我以前也遇到这样的问题.. 见帖子 http://www.amobbs.com/thread-5506453-1-1.html

后来的的解决办法就是先用一个u32的临时变量tmp32读出来, 再赋值到浮点里去, 因为LDR指令不要求地址对齐
如果函数里临时变量少的话, tmp32就会分配到通用寄存器里, 编译器就会操作成: 内存--vldr-->通用寄存器--vmov-->浮点寄存器  , 相对于原来的  内存--vldr-->浮点寄存器  要多一个机器周期, 效率损失不多
作者: pigy0754    时间: 2014-10-18 15:46
armstrong 发表于 2014-10-16 20:01
keil mdk中,你要查看一下__align()的用法,这是解决你问题的关键字;
iar ewarm中,你查看一下#pragma dat ...

回复跟头像都是亮点
作者: JJKwong    时间: 2015-11-11 11:43
深有体会,内存对齐的__align()的用法
作者: ly8008csko    时间: 2015-11-11 16:20
不同编译器处理方法不同,这个太烦躁了
作者: Dragontan    时间: 2015-11-12 17:50
以后遇到这种问题能很快解决
作者: oooios    时间: 2015-11-12 20:23
学习了 已遇到过
作者: jcrorxp    时间: 2015-11-12 20:34
原来这个 看来要避免啊~
作者: brumby    时间: 2015-11-12 20:38
没完全看懂,不明觉厉,还是不知道怎么防范,感觉不可思议,可能我编程方式太常规了,还有就是我一般定义全局变量都是指定地址的。
作者: brumby    时间: 2015-11-12 21:23
写测试程序测试了一下,是浮点数指令 VLDR 等必须4字节对齐,整数运算没有这个问题。
作者: fengyunyu    时间: 2015-11-12 21:49
讨论的满热烈的
作者: 擦鞋匠    时间: 2023-6-29 19:05
不是说stm32原生就支持内存非对齐访问嘛?
作者: xy3dg12    时间: 2023-6-30 11:26
我调过最诡异的两个bug:
1,一个监控设备跑tcp,长时间运行(一周左右)总有一两台死机。仿真器,打trace,一番操作后bug定位在协议栈拷贝数据时越界。把协议栈整个代码翻了个边,而且协议栈是别的项目验证过的,排协议栈有bug。最后怀疑硬件不稳定(新硬件肯定要跑内存测试程序的,所以一开始没有怀疑硬件),经过大量测试,发现因为PCB布线,导致SDRAM上到133外频后,写入特定序列数据(注意是特定序列,所以内存测试程序没有测试出来),会有一个bit从0变1,正好这个位置是拷贝长度。。。。把外频降到100后,非常稳定。
2,另一个ARM7的设备跑nucleus系统,也是一两周后死机。继续仿真器,打trace,一番操作后bug定位在系统内核拷贝数据时越界。这回是真没思路了,nucleus是成熟的RTOS,ARM7本身频率不高,硬件怎么测也没问题。最后是合作的老外,联系了ARM公司。ARM说早期的ARM7中断系统有bug,特殊情况下内核关中断后几条指令内还可以进入中断。。。。
作者: tim4146    时间: 2023-7-3 00:16
xy3dg12 发表于 2023-6-30 11:26
我调过最诡异的两个bug:
1,一个监控设备跑tcp,长时间运行(一周左右)总有一两台死机。仿真器,打trace ...

(引用自53楼)

问题1能找到这个原因真是不容易啊
问题2我们普通人要是遇到只能栽跟头了,因为不是谁都能去和arm联系的
作者: akey3000    时间: 2023-7-3 08:09
xy3dg12 发表于 2023-6-30 11:26
我调过最诡异的两个bug:
1,一个监控设备跑tcp,长时间运行(一周左右)总有一两台死机。仿真器,打trace ...

(引用自53楼)

传说中的高手
作者: leafstamen    时间: 2023-7-3 08:21
xy3dg12 发表于 2023-6-30 11:26
我调过最诡异的两个bug:
1,一个监控设备跑tcp,长时间运行(一周左右)总有一两台死机。仿真器,打trace ...
(引用自53楼)

第一个大概率是16bit的sdram按照8bit写入了
作者: xy3dg12    时间: 2023-7-3 10:31
xy3dg12 发表于 2023-6-30 11:26
我调过最诡异的两个bug:
1,一个监控设备跑tcp,长时间运行(一周左右)总有一两台死机。仿真器,打trace ...
(引用自53楼)

第一个bug好在出错的位置固定,要是出错位置随机那就彻底抓瞎了。
公司配的仿真器功能强大,设置好出错时的读写断点等待触发bug就行了。
再加上一点运气,也前后调了一个多月呢。
作者: xy3dg12    时间: 2023-7-3 10:32
leafstamen 发表于 2023-7-3 08:21
第一个大概率是16bit的sdram按照8bit写入了
(引用自56楼)

SDRAM的底层逻辑就不太清楚了。
可能是内存测试程序不够完善。
作者: gsq19920418    时间: 2023-7-3 10:47
为啥不用 强制对齐指令呢,这块又不求速度
作者: gloryglory    时间: 2023-7-5 07:42
不错,学习
作者: lb0857    时间: 2023-7-5 08:16
xy3dg12 发表于 2023-6-30 11:26
我调过最诡异的两个bug:
1,一个监控设备跑tcp,长时间运行(一周左右)总有一两台死机。仿真器,打trace ...
(引用自53楼)

高手了
一般人就卡在这里估计要走人的项目
作者: tang_qianfeng    时间: 2023-7-5 09:31
F4不支持非对齐访问吗?




欢迎光临 amobbs.com 阿莫电子技术论坛 (https://www.amobbs.com/) Powered by Discuz! X3.4