GCC C ++(ARM)和const指向struct字段的指针

时间:2009-08-16 15:58:15

标签: c++ c embedded arm codesourcery

假设有一个简单的测试代码

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中。另外 - 当”data“声明为:

const int data = (int)(REGISTER)*2;

一切都很好。

C和C ++编译的所有文件都是相同的,唯一的区别是调用编译器 - g ++ for main.cpp,gcc for main.c(警告级别不同,c ++有RTTI和异常禁用)

有没有简单而优雅的方法来克服这个“C ++问题”?我确实需要这样的操作来创建Cortex-M3的位带区域中的位地址的const数组。这是一个错误,或者这可能是C ++编译器的一些奇怪限制吗?

我知道我可以在“C”文件中创建数据对象而只是“extern” - 在C ++中包含它们,但这不是很优雅[;

感谢您的帮助!

7 个答案:

答案 0 :(得分:3)

你有几个问题。你为什么拿一个地址,把它转换成一个整数,乘以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 attribute的特定数据部分:

const volatile int *data __attribute__((section("FLASH")) = /* whatever */;

使用正确的部分名称。如果您不确定那是什么,请使用C编译器生成的目标文件(您说它放在正确的部分中),在其上运行nm,然后查看C编译器将其放入的部分

答案 1 :(得分:3)

正确的解决方案是使用stddef.h标头中的offsetof()宏。

基本上这个:

const int data = (int)(&REGISTER->second)*2;

必须替换为

#include <stddef.h>
const int data = (int)(REGISTER + offsetof(type_t,second))*2;

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

答案 2 :(得分:2)

您是否看过gcc Variable Attributes,也许“部分”可以帮助您完成变量的放置。

答案 3 :(得分:1)

  1. 你不应该倍增指针。
  2. 您不应将指针存储为“int”。
  3. 我确信有一种合法的C ++方法可以做你想要的,但是我无法理解你发布的代码是什么。

答案 4 :(得分:0)

修改

你说你想要多指点,但这很可能是错误的。请注意:(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 */

答案 5 :(得分:0)

我想说对于外设读写的最安全访问,你应该只使用volatile定义和偏移定义。将外围设备地址转换为结构体不会提供任何对齐或偏移保证。

#define UART_BASE_REG ((volatile uint32_t*)0x12341234)
#define UART_STATUS_REG (UART_BASE_REG + 1)

...

答案 6 :(得分:0)

如果我理解正确,您的总体目标将在本段中进行总结:

  

我确实需要这样的操作来创建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)))

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