假设有一个简单的测试代码
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),对象“数据”将按预期进入 Flash。当用 C++ 编译时(如 main.cpp),该对象进入 RAM,并添加额外的代码,其作用只不过是将值从闪存复制到 RAM(该值已经计算出来,只需复制!)。所以编译器可以计算地址,但不知何故不想“只是
使用它”。问题的根源是地址的乘法 - 没有“*2”乘法,两个版本都按预期工作 - “数据”放置在闪存中。另外 - 当“数据”声明为:
const int data = (int)(REGISTER)*2;
也一切都很好 。
(警告级别不同,并且 c++ 禁用了 RTTI 和异常)
C 和 C++ 编译的所有文件都是相同的,唯一的区别是对编译器的调用 - main.cpp 的 g++,main.c 的gcc 有没有简单而优雅的方法来克服这个“C++ 问题”?我确实需要这样的操作来在 Cortex-M3 的位带区域中创建常量地址数组,这是一个错误,还是 C++ 的一些奇怪的限制。我
知道我可以在“C”文件中创建数据对象,然后将它们“extern”包含在 C++ 中,但这不是很优雅[;
谢谢大家的帮助!
Let's say there is a simple test code
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) {};
}
Which is compiled in CodeSourcery G++ (gcc 4.3.2) for bare metal ARM. It also has a very standard linker script.
When compiled in C (as main.c) the object "data" goes into Flash, as expected. When compiled in C++ (as main.cpp) this object goes into RAM, and additional code is added which does nothing more than copy the value from Flash to RAM (the value is already calculated, just copy!). So the compiler can calculate the address, but somehow doesn't want to "just
use it". The root of the problem is the multiplication of the address - without "*2" multiplication both versions work as expected - "data" is placed in Flash. Also - when "data" is declared as:
const int data = (int)(REGISTER)*2;
also everything is fine.
All files for C and C++ compilation are identical, the only difference is the call to compiler - g++ for main.cpp, gcc for main.c (with differences in the level of warnings, and c++ has RTTI and exceptions disabled).
Is there any easy and elegant way to overcome this "C++ problem"? I do require such operations for creating const arrays of addresses of bits in bitband region of Cortex-M3. Is this a bug, or maybe that is some strange limitation of the C++ compiler?
I know that I can create data objects in "C" files and just "extern"-include them in C++, but that's not very elegant [;
Thank you for all help!
发布评论
评论(7)
你有几个问题。为什么要获取一个地址,将其转换为整数,然后乘以 2?您永远不应该通过地址相乘或将地址存储为整数。您应该对指针执行的唯一算术是减去它们(以获得偏移量),或者添加带有偏移量的指针以获得另一个指针。
在 C++ 中,全局值初始化的规则比 C 中稍微宽松一些。在 C 中,值需要初始化为编译时常量;在 C 中,值需要初始化为编译时常量;因此,编译器可以将 const 全局值放入只读内存中。在 C++ 中,值可以初始化为不一定是编译时常量的表达式,并且允许编译器在运行时生成代码来计算初始值。此初始化代码在进入
main()
之前调用,类似于全局对象的构造函数。由于您正在使用常量地址,并且您知道
int
在您的平台上有多大(我假设是 32 位),因此您应该执行如下操作:接下来,您使用
volatile
关键字是完全错误的。易失性告诉编译器不要将变量保存在寄存器中——每次读取变量时都应该从内存中重新加载变量,并且每次写入变量时都应该将其完全写入内存。将局部变量data_copy
声明为易失性
实际上是没有用的,除非您期望另一个线程或信号处理程序意外地开始修改它(非常可疑)。此外,data_copy
只是地址的副本,而不是您尝试读取的寄存器的内容。您应该做的是将
REGISTER
声明为指向易失性的指针——这是易失性
的明确目的之一,用于访问内存- 映射寄存器。你的代码应该是这样的:这使得当你做这样的事情时:
它总是做正确的事情:将 1 写入 0x12345678,将 2 写入 0x12345678,从 0x1234567c 读取,以及从0x2468acf8。
易失性
关键字确保这些读取和写入始终发生,并且它们不会被优化:编译器不会删除对REGISTER.first
的第一次写入,这会如果它是一个常规变量,那么它是多余的。编辑
为了回复您的评论,请参阅 Andrew Medico 对您的评论的回复 - 您实际上是将两个指针之间的差异乘以 2,这是可以的。请注意您的数据类型。我也从未提到过任何有关内核的事情。
您可以使用
section
属性:使用正确的节名称。如果您不确定那是什么,请获取 C 编译器生成的目标文件(您所说的将其放在正确的部分),对其运行
nm
,然后查看 C 编译器的哪个部分把它放进去。You have several problems. Why are you taking an address, converting it to an integer, and multiplying by 2? You should never by multiplying addresses, or storing addresses as ints. The only arithmetic you should ever do with pointers is to subtract them (to obtain an offset), or to add a pointer with an offset to get another pointer.
In C++, the rules for global value initialization are a little more lax than in C. In C, values are required to be initialized to compile-time constants; as a result, the compiler can place
const
global values in read-only memory. In C++, values can be initialized to expressions which aren't necessarily compile-time constants, and the compiler is permitted to generate code at runtime to calculate the initial value. This initialization code is called before entry intomain()
, akin to constructors for global objects.Since you're working with constants addresses, and you know how big an
int
is on your platform (I'm assuming 32 bits), you should just do something like this:Next, your use of the
volatile
keyword is completely wrong.volatile
says to the compiler not to save a variable in a register -- it should be reloaded from memory each time it is read, and it should be fully written to memory every time it is written. Declaring the local variabledata_copy
asvolatile
is practically useless, unless you expect another thread or a signal handler to start modifying it unexpectedly (highly doubtful). Further,data_copy
is just a copy of the address, not the contents of the register you're trying to read.What you should be doing is declaring
REGISTER
as a pointer to a volatile -- that is one of the express purposes ofvolatile
, for accessing memory-mapped registers. Here's what your code should look like:This makes it so that when you do things like this:
It always does the proper thing: a write of 1 to 0x12345678, a write of 2 to 0x12345678, a read from 0x1234567c, and a read from 0x2468acf8. The
volatile
keyword ensures that those reads and writes always happen, and they don't get optimized away: the compiler will not remove the first write toREGISTER.first
, which would be redundant if it were a regular variable.EDIT
In response to your comment, see Andrew Medico's response to your comment -- you're really multiplying the difference between two pointers by 2, which is ok. Just be careful about your data types. I also never mentioned anything about a kernel.
You can get gcc to put variables in a specific data section with the
section
attribute:Use the proper section name. If you're not sure what that is, take the object file generated by the C compiler (which you said puts it in the proper section), run
nm
on it, and see what section the C compiler put it in.正确的解决方案是使用 stddef.h 中的 offsetof() 宏标头。
基本上,
对于 C 和 C++ ,必须将其替换为
然后将对象放置在 Flash 中。
The right solution is using the offsetof() macro from the stddef.h header.
Basically this:
has to be replaced with
And then the object is placed in Flash both for C and C++.
您是否看过 gcc 变量属性 ,也许“节”可以很好地帮助您放置变量。
Have you taken a look at the gcc Variable Attributes, perhaps "section" well help you in the placement of the variable.
我确信有一种合法的 C++ 方法可以完成您想要的操作,但我无法从您发布的代码中理解那是什么。
I'm sure there is a legal C++ way to do what you want, but I can't fathom what that is from the code you've posted.
编辑:
你说你想乘以指针,但这很可能是错误的。请记住:
(int)(REGISTER) * 2
将等于(int)(0x12345678 * 2)
,它可能等于0x2468ACF0
不是你想要的。您可能需要:REGISTER + sizeof(int) * 2
,它是结构体最后一个字节之后的 2 个整数。原始答案:
这看起来像是尝试执行“struct hack" (该示例使用 c99 风格,但它在 C89 中也可以正常工作,只需要有一个 1 的数组作为最后一个元素)。也许你想要的是这样的:
它的常见用法是用于 malloc 分配的结构,如下所示:
EDIT:
You say you want to multiply pointers, but that is very likely wrong. Keep in mind:
(int)(REGISTER) * 2
will equal(int)(0x12345678 * 2)
which equals0x2468ACF0
probably not what you want. You probably want:REGISTER + sizeof(int) * 2
which is 2 integers past the last byte of the struct.Original Answer:
This looks like a failed attempt to do the "struct hack" (The example uses c99 style, but it works just fine in C89 too, just have to have an array of 1 as last element). Probably what you want is something like this:
The common usage of this is for malloc allocated structures like this:
我想说,为了最安全地访问外设读写,您应该只使用易失性定义和偏移量定义。将外设地址转换为结构体不会提供任何类型的对齐或偏移保证。
...
I would say for safest access for peripheral read writes you should just use volatile defines and offset defines. Casting a peripheral address as a struct does not give any sort of alignment or offset guarantees.
...
如果我理解正确的话,本段总结了您的总体目标:
我使用 STM32 的 Keil 编译器,它附带的示例之一包含用于设置和清除位带位的宏:
如果这些不是您想要的,我想它们可以修改以适合您的需求需要。
If I understand correctly, your overall aim is summarised in this paragraph:
I use the Keil compiler for STM32, and one of the examples that comes with it contains macros for setting and clearing bit-banded bits:
If these exactly aren't what you're looking for, I imagine they can be modified to suit your needs.