GCC C ++(ARM)和const指向struct字段的指针
假设有一个简单的测试代码
typedef struct { int first; int second; int third; } type_t; #define ADDRESS 0x12345678 #define REGISTER ((type_t*)ADDRESS) const int data = (int)(®ISTER->second)*2; int main(void) { volatile int data_copy; data_copy = data; while(1) {}; }
这是用于裸机ARM的CodeSourcery G ++(gcc 4.3.2)编译的。 它还有一个非常标准的链接描述文件。
当用C编译(作为main.c)时,对象“data”进入Flash,如预期的那样。 当用C ++编译时(作为main.cpp),这个对象进入RAM,并添加了额外的代码,只是将值从Flash复制到RAM(该值已经计算,只需复制!)。 所以编译器可以计算地址,但不知何故不想“只使用它”。 问题的根源是地址的乘法 – 没有“* 2”乘法,两个版本都按预期工作 – “数据”放在Flash中。 另外 – 当“数据”被声明为:
const int data = (int)(REGISTER)*2;
一切都很好。
用于C和C ++编译的所有文件都是相同的,唯一的区别是对编译器的调用 – 对于main.cpp是g ++,对于main.c是gcc(警告级别不同,c ++有RTTI和exception被禁用)。
有没有简单而优雅的方法来克服这个“C ++问题”? 我确实需要这样的操作来创建Cortex-M3的位带区域中的位地址的const数组。 这是一个错误,或者这可能是C ++编译器的一些奇怪限制?
我知道我可以在“C”文件中创建数据对象而只是“extern” – 在C ++中包含它们,但这不是很优雅[;
谢谢你的帮助!
你有几个问题。 你为什么拿一个地址,把它转换成一个整数,乘以2? 您永远不应该通过乘以地址或将地址存储为整数。 你应该用指针做的唯一算法是减去它们(以获得偏移量),或者添加带有偏移量的指针以获得另一个指针。
在C ++中,全局值初始化的规则比在C中略宽一些。在C中,需要将值初始化为编译时常量; 因此,编译器可以将const
全局值放在只读内存中。 在C ++中,值可以初始化为不一定是编译时常量的表达式,并且允许编译器在运行时生成代码以计算初始值。 在进入main()
之前调用此初始化代码,类似于全局对象的构造函数。
既然你正在使用常量地址,并且你知道你的平台上有多大的int
(我假设是32位),你应该这样做:
接下来,您使用volatile
关键字是完全错误的。 volatile
告诉编译器不要将变量保存在寄存器中 – 每次读取时都应该从内存中重新加载,并且每次写入时应该完全写入内存。 将局部变量data_copy
声明为volatile
实际上是无用的,除非您期望另一个线程或信号处理程序意外地开始修改它(非常值得怀疑)。 此外, data_copy
只是地址的副本,而不是您尝试读取的寄存器的内容。
你应该做的是将REGISTER
声明为指向volatile的指针 – 这是volatile的明确目的之一,用于访问内存映射寄存器。 这是您的代码应该是什么样子:
#define REGISTER (*(volatile type_t *)ADDRESS) #define DATA (*(const volatile int *)((ADDRESS+4)*2))
这使得当你做这样的事情时:
REGISTER.first = 1; REGISTER.first = 2; int x = REGISTER.second; int y = DATA;
它始终做正确的事情:写入1到0x12345678,写入2到0x12345678,读取0x1234567c,读取0x2468acf8。 volatile
关键字确保始终发生这些读取和写入,并且它们不会被优化掉:编译器不会删除对REGISTER.first
的第一次写入,如果它是常规变量,这将是多余的。
编辑
在回答您的评论时,请参阅Andrew Medico对您的评论的回复 – 您实际上是将两个指针之间的差异乘以2,这没关系。 请注意您的数据类型。 我也从未提及有关内核的任何内容。
您可以使用gcc将变量放在具有section
属性的特定数据部分中:
const volatile int *data __attribute__((section("FLASH")) = /* whatever */;
使用正确的部分名称。 如果您不确定那是什么,请使用C编译器生成的目标文件(您说它放在正确的部分中),在其上运行nm
,然后查看C编译器将其放入的部分。
正确的解决方案是使用stddef.h头文件中的offsetof()宏。
基本上这个:
const int data = (int)(®ISTER->second)*2;
必须被替换
#include const int data = (int)(REGISTER + offsetof(type_t,second))*2;
然后将对象放在Flash中,用于C和C ++。
你有没有看过gcc 变量属性 ,也许“section”可以帮助你放置变量。
- 你不应该乘以指针。
- 您不应该将指针存储为“int”。
我确信有一种合法的C ++方法可以做你想做的事情,但我无法理解你发布的代码是什么。
编辑 :
你说你想要多指点,但这很可能是错误的。 请记住: (int)(REGISTER) * 2
将等于(int)(0x12345678 * 2)
,它等于0x2468ACF0
可能不是你想要的。 您可能需要: REGISTER + sizeof(int) * 2
,它是结构的最后一个字节后面的2个整数。
原答案:
这似乎是尝试执行“ struct hack ”失败的尝试(该示例使用c99样式,但它在C89中也可以正常工作,只需要将数组1作为最后一个元素)。 可能你想要的是这样的:
typedef struct { int first; int second; int third; unsigned char data[1]; /* we ignore the length really */ } type_t; type_t *p = (type_t *)0x12345678; p->data[9]; /* legal if you **know** that there is at least 10 bytes under where data is */
这个的常见用法是对于malloc分配的结构,如下所示:
type_t *p = malloc((sizeof(int) * 3) + 20) /* allocate for the struct with 20 bytes for its data portion */
我想说为了最安全地访问外设读写,你应该只使用volatile定义和偏移定义。 将外围设备地址转换为结构体不会提供任何对齐或偏移保证。
#define UART_BASE_REG ((volatile uint32_t*)0x12341234) #define UART_STATUS_REG (UART_BASE_REG + 1)
…
如果我理解正确,您的总体目标将在本段中进行总结:
我确实需要这样的操作来创建Cortex-M3的位带区域中的位地址的const数组。 这是一个错误,或者这可能是C ++编译器的一些奇怪限制?
我将Keil编译器用于STM32,其中的一个示例包含用于设置和清除位带状位的宏:
#define RAM_BASE 0x20000000 #define RAM_BB_BASE 0x22000000 #define Var_ResetBit_BB(VarAddr, BitNumber) \ (*(vu32 *) (RAM_BB_BASE | ((VarAddr - RAM_BASE) << 5) | ((BitNumber) << 2)) = 0) #define Var_SetBit_BB(VarAddr, BitNumber) \ (*(vu32 *) (RAM_BB_BASE | ((VarAddr - RAM_BASE) << 5) | ((BitNumber) << 2)) = 1) #define Var_GetBit_BB(VarAddr, BitNumber) \ (*(vu32 *) (RAM_BB_BASE | ((VarAddr - RAM_BASE) << 5) | ((BitNumber) << 2)))
如果这些不是你想要的,我想他们可以根据你的需要进行修改。