前言##
前几天,主管找我问关于定义变量起始地址对齐方式的问题。下面介绍一下问题:上位机生成了一个参数数组 char para[36541] = {…} ;数组比较大,已经内建好了结构体,只要按照指针指到头部地址即可正常读取出来,但是由于处理器读取int 或者 float 数据类型比较要求数据地址是四字节对齐的,而我们这个数组存放在内存中是以两个字节对齐的,这就导致了CPU无法读取数据并死机在了读取float数据类型处;
主管提出,能否直接指定该数组的存在于内存中的起始地址?我一想,确实是,只要头地址对了那就啥问题也没有了。
主管又补充了一句,他测试过了 #pragma pack(4) 这种编译命令,实际效果是没效果,于是我上网查了下,这个命令确实是数据对齐的命令,但是只是对于结构体内部。对于数组头地址,我开始觉得有点挑战了;
而且方法要简单,不要影响程序的运行效率问题;
问题尝试
今天在处理上次开会中提到的内存分配的问题,改着改着忽然就灵光一闪,觉得这个问题应该是可以解决的:
我拿出了自己使用的比较利索的C语言工具->IAR workbench for ARM; 测试平台当然还是最熟悉的Kinetis 系列的 K66 啦!
先定义以下变量:测试看看存放于内存中的位置,代码如下:
char aa[2] = {1,2};
查看地址的方式有两个:
- 通过IAR编译器生成的MAP文件进行查找
- 就是通过IAR DEBUG工具中的 watch工具进行查看
而我这种偷懒王必须采用第二种方式啊,多么直观,哈哈哈哈;
如下是我通过IAR Debug watch中查看到的相关信息:
Expression | Value | Location | Type |
---|---|---|---|
aa | <<a>array>”??” | 0x1FFF0070 | char[2] |
bb | <<a>array>”??” | 0x1FFF0072 | char[2] |
可以看出上面aa存放地址是 0x1FFF0070 而bb的地址存放在0x1FFF0072地址上,bb的起始地址是一个2字节对齐的地址,并非四地址对齐的地址。
明确目标,向前进发
准备修改bb的首地址到一个四字节对齐的地址上,或者八字节对齐的地址上
目标明确过后就是尝试了,我首先尝试了下主管说过的
#pragma pack(x) ;
这类编译命令,不出意外,一点影响都没有;
直到今天,一个偶然中想到了是否可以使用IAR 链接时候使用的ICF文件(链接文件)进行处理;
好,说做就做:开始尝试,写了一个简单的链接代码测试一下:
//define block to place test value
define symbol m_data_start = 0x1FFF0000;
define symbol m_data_end = 0x1FFFFFFF;
define memory mem with size = 4G;
define region test_region = mem:[from m_data_start to m_data_end];
define block test_block with alignment = 4 {section .test};
place in test_region {block test_block};
这里定义了一下链接文件(*.icf)将 .test这个段放在了0x00000410 ~ 0x1FFFFFFF这个区段中以四字节对齐的地址上;
然后回去在变量定义中修改成如下代码:
char aa[2] = {1,2};
#pragma location = ".test"
char bb[2] = {3,4};
这里意思是数组aa依然按照编译器缺省规则编译,而数组bb则存放到 .test段中;
究竟是否成功呢?打开IAR Debug watch:惊喜啊,真的是啊:
以下是4字节对齐aa、bb首地址
Expression | Value | Location | Type |
---|---|---|---|
aa | <<a>array>”??” | 0x1FFF0070 | char[2] |
bb | <<a>array>”??” | 0x1FFF04A8 | char[2] |
好家伙,真的是成了四字节对齐的地址了(0x1FFF04A8 % 4 == 0);为了确保是这个样子,多实验几下看看,看看是不是对的,于是将链接中关于对齐部分代码修改如下:
define block test_block with alignment = 8 {section .test};
再使用IAR debug watch查看一下地址:
以下是8字节对齐的aa、bb首地址
Expression | Value | Location | Type |
---|---|---|---|
aa | <<a>array>”??” | 0x1FFF0070 | char[2] |
bb | <<a>array>”??” | 0x1FFF04A8 | char[2] |
地址竟然和上面一样,使用计算器计算一下发现(0x1FFF04A8 % 8 == 0),确定是8字节对齐的。
我心里还是有点疑虑,看看16、32字节对齐会有啥样子?
以下是16字节对齐的aa、bb首地址
Expression | Value | Location | Type |
---|---|---|---|
aa | <<a>array>”??” | 0x1FFF0070 | char[2] |
bb | <<a>array>”??” | 0x1FFF04B0 | char[2] |
新地址计算一下(0x1FFF04B0 % 16 == 0 )
以下是32字节对齐的aa、bb首地址
Expression | Value | Location | Type |
---|---|---|---|
aa | <<a>array>”??” | 0x1FFF0070 | char[2] |
bb | <<a>array>”??” | 0x1FFF04C0 | char[2] |
新地址计算一下(0x1FFF04C0 % 32 == 0 )
结论
通过这一系列的尝试。发现ICF文件(链接文件)中的块alignment关键字可以使块的首地址按照2^x(2,4,8,16,32…)方式对齐;而我们定义这一个块就存放一个变量,那么这个变量的首地址的对齐方式就是一个可控的值。
展望
由于项目中使用的参数文件是上位机生成的一个数组,按道理这个参数是不需要被被修改的,本着提高CPU的效率考虑(CPU复位时候程序初始化需要搬运.data段到RAM中),我们可以定义到该变量到CODE中成为一个常量表,可以提高系统启动的速率;
以下为改进的icf文件关键代码:
//define block to place test value
define symbol m_text_start = 0x00000410;
define symbol m_text_end = 0x001FFFFF;
define memory mem with size = 4G;
define region test_region = mem:[from m_text_start to m_text_end];
define block test_block with alignment = 4 {section .test};
place in test_region {block test_block};
以下为改进了的测试数组定义
char aa[2] = {1,2};
#pragma location = ".test"
const char bb[2] = {3,4};
以下是通过IAR Debug Watch 所观察到的变量数据:
Expression | Value | Location | Type |
---|---|---|---|
aa | <<a>array>”??” | 0x1FFF0070 | char[2] |
bb | <<a>array>”??” | 0x00013748 | char[2] |
可以发现该地址存在于flash空间中,仍然是一个4字节对齐的地址,成功,哈哈哈哈,这次任务完成;
寄语
楼主文笔特别差,很多时候表词不达意,或者用词错误,亦或者对于东西理解不深入等等。但是希望我的分享能够解决你的实际问题。也希望你阅读过程中发现对文章的技术或者其他方法有怀疑的提出反馈,一起讨论学习,一起进步。谢谢阅读。