简体   繁体   中英

Initialization of a pointer array

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;

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

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?

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

The address of an element (eg &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).

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;
 }  
}

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, ie 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM