GCC C++ (ARM) and const pointer to struct field

淺唱寂寞╮ 提交于 2019-12-10 08:09:15

问题


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)(&REGISTER->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!


回答1:


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.




回答2:


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

Basically this:

const int data = (int)(&REGISTER->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++.




回答3:


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




回答4:


  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.




回答5:


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



回答6:


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)

...




回答7:


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.



来源:https://stackoverflow.com/questions/1284619/gcc-c-arm-and-const-pointer-to-struct-field

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!