【问题标题】:GCC C++ (ARM) and const pointer to struct fieldGCC C++(ARM)和指向结构字段的常量指针
【发布时间】:2009-08-16 15:58:15
【问题描述】:

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

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) {};
}

在 CodeSourcery G++ (gcc 4.3.2) 中为裸机 ARM 编译。它还有一个非常标准的链接器脚本。

在 C 中编译时(作为 main.c),对象“数据”按预期进入 Flash。当用 C++ 编译时(作为 main.cpp),这个对象进入 RAM,并且添加了额外的代码,这些代码只是将值从 Flash 复制到 RAM(值已经计算,只需复制!)。所以编译器可以计算地址,但不知何故不想“只是 使用它”。问题的根源是地址的乘法 - 没有“* 2”乘法,两个版本都按预期工作 - “数据”被放置在闪存中。此外 - 当“数据”声明为:

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

一切都很好。

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

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

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

感谢大家的帮助!

【问题讨论】:

    标签: c++ c embedded arm codesourcery


    【解决方案1】:

    你有几个问题。为什么要取一个地址,将其转换为整数,然后乘以 2?您永远不应该将地址相乘,或将地址存储为整数。你应该对指针做的唯一算术是减去它们(以获得偏移量),或者添加一个带有偏移量的指针以获得另一个指针。

    在 C++ 中,全局值初始化的规则比 C 中的要宽松一些。在 C 中,值需要初始化为编译时常量;因此,编译器可以将const 全局值放在只读内存中。在 C++ 中,值可以初始化为不一定是编译时常量的表达式,并且允许编译器在运行时生成代码来计算初始值。此初始化代码在进入 main() 之前调用,类似于全局对象的构造函数。

    由于您使用的是常量地址,并且您知道int 在您的平台上有多大(我假设是 32 位),您应该这样做:

    接下来,您对volatile 关键字的使用是完全错误的。 volatile 告诉编译器不要将变量保存在寄存器中——每次读取时都应该从内存中重新加载它,并且每次写入时都应该完全写入内存。将局部变量 data_copy 声明为 volatile 实际上是没有用的,除非您期望另一个线程或信号处理程序开始意外地修改它(非常值得怀疑)。此外,data_copy 只是地址的副本,而不是您尝试读取的寄存器的内容。

    应该做的是将REGISTER 声明为指向易失性的指针——这是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;
    

    它总是做正确的事:向 0x12345678 写入 1,向 0x12345678 写入 2,从 0x1234567c 读取,从 0x2468acf8 读取。 volatile 关键字确保这些读取和写入始终发生,并且它们不会被优化掉:编译器不会删除对REGISTER.first 的第一次写入,如果它是一个常规变量,这将是多余的。

    编辑

    在回复您的评论时,请参阅 Andrew Medico 对您的评论的回复 - 您实际上是在将两个指针之间的 差异 乘以 2,这没关系。请注意您的数据类型。我也从未提及任何关于内核的事情。

    您可以使用section attribute 让 gcc 将变量放入特定的数据部分:

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

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

    【讨论】:

    • 呃……裸金属人!没有内核,我真的必须乘以指针并以任何方式存储地址,我知道我在做什么以及为什么要做。看看对第一个答案的评论。易失性“data_copy”的存在只是为了让编译器不会删除未使用的“数据”。只需坚持主要问题 - “如何将结构字段的相乘地址放在闪存中”。
    • 明确地将 var 放在 .text 部分并不能解决问题。 “0”的值被放置在闪存中(即“数据”对象),并且“初始化函数”仍在尝试将值从闪存“复制”到...闪存...我知道我想要将两个地址的差相乘,但这并不重要——这确实是最简单的测试用例,不会因为其他无用的东西而臃肿,所以请忽略“乘以指针是愚蠢的”,因为这不是重点。
    【解决方案2】:

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

    基本上是这样的:

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

    必须替换为

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

    然后将对象放置在 C 和 C++ 的 Flash 中。

    【讨论】:

      【解决方案3】:

      你有没有看过gcc Variable Attributes,也许“部分”很好地帮助你放置变量。

      【讨论】:

        【解决方案4】:
        1. 您不应该将指针相乘。
        2. 不应将指针存储为“int”。

        我确信有一种合法的 C++ 方法可以做你想做的事,但我无法从你发布的代码中理解那是什么。

        【讨论】:

        • 相信我——乘以指针正是我想要做的。不要介意将其存储为“int”,这不是重点。请记住 - 我使用的是裸机 ARM 内核。地址空间中有一个“外围区域”,以及该区域的位带别名。 “外围区域”中的任何位都映射到位带区域中的一个 uin32_t。计算位带区域地址的公式如下: (0x42000000+(((unsigned long)address)-0x40000000)*32+(bit)*4) 由于标头中的 STM32 寄存器按结构 I 组织面对上述问题。
        • 该表达式实际上不是乘以指针 - 它乘以两个地址之间的差(这是一个普通数字)。我建议将其重写为“(uint32_t*)0x42000000 + (addr - (uint32_t*)0x40000000)*32 + bit*4”。
        • Re.2.: 是的,但我尽量提供最简单的测试用例。以您建议的方式重写宏不会改变任何内容,因为 C 编译器以某种方式知道该字段的地址,而 C++“认为他不知道该地址”......这真的很奇怪 [;正如我在问题中所写 - 当我使用普通地址时 - 一切都很好,当我想使用结构字段的地址时问题就开始了。
        • Freddie Chopin:作为一般建议,以后如果您提出问题并且您知道自己在做不可靠的事情,但您知道自己在做什么以及为什么,请务必在问题,否则人们会 - 非常正确地 - 将其识别为问题。
        【解决方案5】:

        编辑

        你说你想乘以指针,但这很可能是错误的。请记住:(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 */
        

        this 的常见用法是用于 malloc 分配的结构,如下所示:

        type_t *p = malloc((sizeof(int) * 3) + 20) /* allocate for the struct with 20 bytes for its data portion */
        

        【讨论】:

        • 回复。编辑:我对问题中发布的代码的期望值是 0x2468acf8。
        【解决方案6】:

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

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

        ...

        【讨论】:

        • 不过,你问的不是主要问题。我知道你写了什么,但是有一个问题——已经创建了包含所有寄存器的标题,我真的不想编辑 600kB 的文本......
        【解决方案7】:

        如果我理解正确,您的总体目标总结在此段中:

        我确实需要这样的操作来在 Cortex-M3 的位带区域中创建位地址的 const 数组。这是一个错误,还是 C++ 编译器的一些奇怪限制?

        我使用 STM32 的 Keil 编译器,它附带的示例之一包含用于设置和清除位带位的宏:

        #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)))
        

        如果这些不是您正在寻找的,我想可以对其进行修改以满足您的需求。

        【讨论】:

        • 宏在这里不是问题,因为我知道公式。问题是,当您的“VarAddr”类似于(&REGISTER->FIELD)时,C++ 不希望在 Flash 中存储这些地址。正如我在其中一个回复中所写,使用 offsetof() 宏可以解决这里的真正问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-12-30
        • 2020-10-23
        • 1970-01-01
        • 1970-01-01
        • 2021-10-15
        • 2012-02-07
        相关资源
        最近更新 更多