假设有一个简单的测试代码
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中。另外 - 当”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 ++中包含它们,但这不是很优雅[;
感谢您的帮助!
答案 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)(®ISTER->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)
我确信有一种合法的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)))
如果这些不是您想要的,我想他们可以根据您的需要进行修改。