海湾合作委员会 C++ (ARM) 和指向结构体字段的 const 指针

发布于 2024-08-02 05:00:39 字数 969 浏览 4 评论 0 原文

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

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),对象“数据”将按预期进入 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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

谁的年少不轻狂 2024-08-09 05:00:39

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

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

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

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

应该做的是将REGISTER声明为指向易失性的指针——这是易失性的明确目的之一,用于访问内存- 映射寄存器。你的代码应该是这样的:

#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。 易失性关键字确保这些读取和写入始终发生,并且它们不会被优化:编译器不会删除对REGISTER.first的第一次写入,这会如果它是一个常规变量,那么它是多余的。

编辑

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

您可以使用 section 属性

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

使用正确的节名称。如果您不确定那是什么,请获取 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 into main(), 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 variable data_copy as volatile 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 of volatile, for accessing memory-mapped registers. Here's what your code should look like:

#define REGISTER (*(volatile type_t *)ADDRESS)
#define DATA (*(const volatile int *)((ADDRESS+4)*2))

This makes it so that when you do things like this:

REGISTER.first = 1;
REGISTER.first = 2;
int x = REGISTER.second;
int y = DATA;

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 to REGISTER.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:

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

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.

信仰 2024-08-09 05:00:39

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

基本上,

const int data = (int)(®ISTER->second)*2;

对于 C 和 C++ ,必须将其替换为

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

然后将对象放置在 Flash 中。

The right solution is using the offsetof() macro from the stddef.h header.

Basically this:

const int data = (int)(®ISTER->second)*2;

has to be replaced with

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

And then the object is placed in Flash both for C and C++.

盗琴音 2024-08-09 05:00:39

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

Have you taken a look at the gcc Variable Attributes, perhaps "section" well help you in the placement of the variable.

赠我空喜 2024-08-09 05:00:39
  1. 你不应该乘以指针。
  2. 您不应该将指针存储为“int”。

我确信有一种合法的 C++ 方法可以完成您想要的操作,但我无法从您发布的代码中理解那是什么。

  1. You shouldn't be multiplying pointers.
  2. You shouldn't be storing pointers as "int".

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.

我很OK 2024-08-09 05:00:39

编辑

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

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 equals 0x2468ACF0 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:

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 */

The common usage of this is for malloc allocated structures like this:

type_t *p = malloc((sizeof(int) * 3) + 20) /* allocate for the struct with 20 bytes for its data portion */
女中豪杰 2024-08-09 05:00:39

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

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

...

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.

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

...

披肩女神 2024-08-09 05:00:39

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

我确实需要这样的操作来创建 Cortex-M3 位带区域中的位地址常量数组。这是一个错误,还是 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)))

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

If I understand correctly, your overall aim is summarised in this paragraph:

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 use the Keil compiler for STM32, and one of the examples that comes with it contains macros for setting and clearing bit-banded bits:

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

If these exactly aren't what you're looking for, I imagine they can be modified to suit your needs.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文