tags:

views:

108

answers:

4

Hi all,

I'm facing a problem initializing an array with pointers to members of a structure. The structure members have to be accessed through a structure pointer. The reason for this is we initialize the pointer at runtime to a memory mapped address location. The following code snippet is an example of the problem;

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
  long* lp;
}T;

typedef struct
{
  long l;
}F;

F* f;

T t[] =
{
    { &f->l }
};

void init (void)
{
  f = (F*) 0x08000100; 
}

int main (void)
{
  init();

  return EXIT_SUCCESS;
}

The compiler output is the following;

gcc -O0 -g3 -Wall -c
-fmessage-length=0 -osrc\Test.o ..\src\Test.c ..\src\Test.c:18: 
error: initializer element is not constant
..\src\Test.c:18: error: (near initialization for `t[0].lp')
..\src\Test.c:18: error: initializer element is not constant
..\src\Test.c:18: error: (near initialization for `t[0]') 
Build error occurred, build is stopped

The problem here is we initialize the pointer at runtime, the compiler doesn't know where it can find the structure members. We cannot work around the structure pointer as we don't wan't to use the linker script for this.

Any ideas how to get around this one?

+3  A: 
T t[] =
{
    { &f->l }
};

The address of an element (e.g. &f->l) is only known at run-time.

Such a value cannot be used for compile-time initialization (which is what's being done here).

Blank Xavier
Yes that makes sense. But what if i would define f as a const pointer to the 'magic address'; `F* const f = (F*) 0x08000100;`
Swiebertje
Then you've cast an integer value into a pointer. That's viable as a compile-time init. However, unless you have a hardware register or somesuch, it's hard to see it being useful?
Blank Xavier
The address is a hardware location, namely memory mapped eeprom. Of-course the example above is just for discussion's sake, if i had to implement it this way i would define a macro with the eeprom address in a hardware depended header file and use that macro for the initialization of the pointer; `F* const f = (F*) EEPROM_BASE;`. Anyway the compiler does not seem to accept this either, it's still giving the same compilation error.
Swiebertje
A: 

The t[] array cannot be filled out until runtime - because the address of F isn't known until runtime.

You could initialize T[] to {NULL} and patch it in post-init.

Another approach is to initialize the members of T to just simply be the offset within the structure, and after you init f, to walk through the array and adjust the pointer locations by adding the address of f. This technique is similar to what is often used in linking.

Something like this:

#define MEMBER_OFFSET_OF(a,b) &(((a*)0)->b)

T t[] = 
{
   {(long*)MEMBER_OFFSET_OF(F, l)}
};
const int numElementsInT = sizeof(t) / sizeof(t[0]);

void init()
{
 f = (F*) 0x08000100; 
 for (int i= 0; i < numElementsInT; i++)
 {
   t[i].lp += (unsigned int)f;
 }  
}
Scott S
+1: That's a neat `MEMBER_OFFSET_OF` macro. Didn't see this before. :)
Frerich Raabe
What's wrong with the standard offsetof macro? (It even does the right thing with type and parentheses.)
Roger Pate
Actually, there's a worse problem here: `t[i].lp += (unsigned)f`. That is not the same as `t[i].lp = (long*)((unsigned)t[i].lp + (unsigned)f)`, which does what you describe in the text.
Roger Pate
Good find - pointer arithmetic would be adding sizeof(long) * offset instead of just the offset. I was intending this as pseudocode so I didn't bother compiling or testing it.
Scott S
A: 

Technically speaking for a C90 compiler there is no way around this. For the initialization idiom,

declarator = initialization sequence

the initialization sequence needs to be a constant expression, i.e. one which can be computed at compile-time or at link-time. So,

int a;
int *b[] = { &a };

works, while

void foo() {
    int a;
    int *b[] = { &a };
}

will not because the address of the automatic a isn't computable before runtime.

If you switch to C99, the latter will work. Your code however still is beyond what a C99 compiler can precompute. If you switch to C++ your code would work, at least Comeau doesn't object. Edit: of course Roger is correct in that this doesn't solve your problem of having an incorrect dereferencing through a NULL pointer.

slartibartfast
A: 

Lets imagine that you could use non-constant data to initialize a global: you still have a huge problem.

When t is initialized, f still has an indeterminate value: this happens before init() executes and assigns your magic address. Because of this, even if you could use &f->l, you'd have to reset all places it's been used, anyway.

Roger Pate