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)(&REGISTER->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)(&REGISTER->second)*2; 

必须被替换

 #include  const int data = (int)(REGISTER + offsetof(type_t,second))*2; 

然后将对象放在Flash中,用于C和C ++。

你有没有看过gcc 变量属性 ,也许“section”可以帮助你放置变量。

  1. 你不应该乘以指针。
  2. 您不应该将指针存储为“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))) 

如果这些不是你想要的,我想他们可以根据你的需要进行修改。

Interesting Posts