Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 738
Freddie Chopin Avatar asked Jan 23 '23 07:01

Freddie Chopin


1 Answers

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.

like image 136
Adam Rosenfield Avatar answered Jan 31 '23 13:01

Adam Rosenfield