4

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

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++ 中,但这不是很优雅 [;

谢谢大家的帮助!

4

7 回答 7

3

正确的解决方案是使用 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 中。

于 2009-08-17T06:51:41.037 回答
3

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

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

由于您使用的是常量地址,并且您知道平台上有多大int(我假设是 32 位),因此您应该执行以下操作:

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

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

编辑

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

section您可以使用以下属性让 gcc 将变量放在特定数据部分中:

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

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

于 2009-08-16T16:20:20.587 回答
2

您是否看过 gcc变量属性,也许“部分”可以很好地帮助您放置变量。

于 2009-08-16T16:38:01.887 回答
1
  1. 你不应该乘以指针。
  2. 您不应该将指针存储为“int”。

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

于 2009-08-16T16:04:42.980 回答
0

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

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

...

于 2009-08-16T16:54:05.047 回答
0

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

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

如果这些不是您想要的,我想它们可以修改以满足您的需求。

于 2009-08-17T08:18:43.717 回答
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 */
于 2009-08-16T16:11:50.797 回答