10

I have that may be a problem of declaration :

I declare an array of const int :

const int my_array[] = {
    // data...
}

Then I need to declare another array of bytes of the form :

00 aa 01 bb 02 cc

where aabbcc is the 24-bits address in memory of the const int (I precise I code for a very particular platform, this explains that), so I wrote :

const char my_other_array[] = {
    00, (my_array >> 16) & 0xFF, 01, (my_array >> 8) & 0xFF, 02, my_array & 0xFF
}

but I get this error :

error: invalid operands to binary >>
error: initializer element is not constant

I thought about casting my_array :

const char my_other_array[] = {
    00, (((const u32) my_array) >> 16) & 0xFF, 01, (((const u32) my_array) >> 8) & 0xFF, 02, ((const u32) my_array) & 0xFF
}

but then I get a warning + error :

warning: initializer element is not computable at load time
error: initializer element is not computable at load time

What am I doing wrong ?

Here's the actual code, for those asking (I cut the irrelevant parts) :

#include <genesis.h>
#include "snake.h"

const u32 snake_patterns[] = {
    0x00024666,
    // ... some 100ths of lines
};

const u16 temp[] = {
    1, 0x9370, 0x9400, ((const u32) snake_patterns) & 0xFF, (((const u32) snake_patterns) >> 8) & 0xFF, (((const u32) snake_patterns) >> 16) & 0xFF
};

You'll notice things are a little more complicated, but I think the previous basic example (fixed with the appropriate brackets) shows the problem in a clearer way. Some may recognize a list of DMA calls for the Genesis VDP.

T. Tournesol
  • 310
  • 2
  • 14

6 Answers6

7

The elements you use to initialize your arrays need to be constant expressions. These are defined in Section 6.6 of the C99 standard or the same place in C11. See paragraph 7:

More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or evaluate to, one of the following:

  • an arithmetic constant expression,
  • a null pointer constant,
  • an address constant, or
  • an address constant for an object type plus or minus an integer constant expression.

Now my_array is an address constant, but all you're allowed to do to it is add or subtract an integer constant. By shifting or masking, you're creating something that is no longer a constant expression, thus not allowable in an initializer.

I guess the rationale for this restriction is that C is intended to be usable for relocatable code, where the program's location in memory may not be known until it's loaded in preparation for execution. On such a system, references to addresses within the program have to be filled in by the loader, based on a table within the binary that it reads at load time (e.g. "At relative address 0x12345678 within the program, fill in the absolute address of the object my_array once it is known"). This table usually has a fairly restrictive format, and probably has a way to express constant offsets ("fill in the absolute address of the object my_array, plus 42") but typically won't support arbitrary arithmetic.

Probably the simplest solution for you is to make my_other_array not be const and fill it in at runtime, by writing a function that extracts the necessary bits of the address of my_array and inserts them into my_other_array, and call this function before you need to use my_other_array.

If it's important for some reason that my_other_array be already filled in when the program is loaded, and your target platform is such that you know where in memory the program will be located, then you might be able to use the facilities of your assembler or linker to achieve what you want. But of course this would be system-specific.

(Edit: You've mentioned in another comment that this array needs to go in ROM. If so, then I think my last suggestion is your only hope. You may want to post another question about how / whether you can do this with the particular toolchain you're using.)

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • Thanks for your detailed answer. I naively thought that any calulation involving only constants would be suitable. I don't master the toolchain (it uses an old version of GCC with a premade make file) so I think I'll declare a zeroed array of the desired size, and write a small python script that'll read the symbol table and fill the zeroed array directly in the compiled binary. How awful... – T. Tournesol Mar 23 '16 at 16:19
  • @T.Tournesol: If you're using GCC, you might also be using GNU ld as your linker, which has a rather powerful scripting language. I don't know for sure whether it has this specific functionality, but it is worth checking. – Nate Eldredge Mar 23 '16 at 16:23
  • @T.Tournesol: It's all part of the fun of low-level programming! – Nate Eldredge Mar 23 '16 at 16:27
  • But it's in fact high-level !!! I was given the choice between C and plain 68000 asm :) – T. Tournesol Mar 23 '16 at 16:56
6

"What am I doing wrong?"

The most immediate thing I see is....

this is not an array:

const int my_array = { /* elements */ }

this is an array:

const int my_array[N] = { /* N elements */ };

Additionally, don't ignore that error message! it speaks truth!

error: initializer element is not constant

You are using something that is not a constant, namely "my_array", to initialise array elements. "my_array" will evaluate to a pointer to the first element of the array, and this value is not known at compile time.

Erik Nyquist
  • 1,267
  • 2
  • 12
  • 26
  • Sorry, I mistyped my example. I checked the actual code and didn't forget the brackets, so it's not so simple :) – T. Tournesol Mar 23 '16 at 15:48
  • well, then nobody will be able to help you. You need to post the *actual code* that you are working with. – Erik Nyquist Mar 23 '16 at 15:50
  • I thought it was more comfortable to paste a code showing exactly the proble, and this code does, but if you want the actual code, I'll paste it at the end of the message. – T. Tournesol Mar 23 '16 at 15:52
  • That approach suggests you know exactly where the problem is. If that were the case, would you even be here? :) – Erik Nyquist Mar 23 '16 at 15:55
3

Assuming that you want to evaluate my_array[X] but not my_array:

That's not supported by C; a shorter example might be

int const   foo[] = { 0 };
int         bar[] = { foo[0] };

Despite the const keyword, the C standard does not allow to evaluate this at compile time.

You can try to place it into function context

int const   foo[] = { 0 };
int         bar[1];

void init(void)
{
    int     tmp[] = { foo[0] };

    memcpy(bar, tmp, sizeof tmp);
}

Alternatively, you can use an external tool to generate a header file with bar[] content.

EDIT:

When your platform's endianess matches (linker places address of the array in the way how it is expected by the DMA controller), you can try

#include <stdint.h>

static int const    foo[] = { 23 };
struct dma {
    uint16_t    a;
    uint16_t    b;
    uint16_t    c;
    void const  *p;
} __attribute__((__packed__));

struct dma const    tmp = {
    .a  = 1,
    .b  = 0x9370,
    .c  = 0x9400,
    .p  = foo,
};
ensc
  • 6,704
  • 14
  • 22
  • The first approach is not suitable because my_other_array must go in ROM. The second would be incredibely convoluted since I need the symbol table with the actual location of my_array to generate the data in my_other_array... It's a much more painful problem than I thought... – T. Tournesol Mar 23 '16 at 16:04
  • Thanks a lot for you answer ! I wish I could select two answers :) – T. Tournesol Mar 23 '16 at 16:21
  • The EDIT is clever. Unfortunatelt, the endianness don't match here (and to make things worse, it's in fact addr/2 that must be sent, interleaved by 0x95, 0x96 and 0x97). – T. Tournesol Mar 23 '16 at 16:55
2

It sounds like you want one "variable" in ROM to store the address of some other variable.

Many platforms have very little RAM, so putting as much data in (program memory) ROM rather than scarce RAM is a good idea.

portable approach using standard C

Perhaps using a "function context" as ensc suggested is close enough?

#include <genesis.h>
#include "snake.h"
#include <string.h>

const u32 snake_patterns[] = {
    0x00024666,
    // ... some 100ths of lines
};

int test_system( int a, unsigned char * dest[] ){
    const u16 temp[] = {
        1, 0x9370, 0x9400, // const, so stored in ROM
        ((const u32) snake_patterns) & 0xFF,
        (((const u32) snake_patterns) >> 8) & 0xFF,
        (((const u32) snake_patterns) >> 16) & 0xFF
    };
    // ... then do something with that array, perhaps
    memcpy(dest, temp, 12);
}

int main(void){
    unsigned char * buffer[80];
    int mangled_address = (u32) snake_patterns;
    test_system( mangled_address, buffer );
    printf("result: %s", buffer);
}

nonstandard extensions

Several C compilers have a few features that let you tell the compiler to put things in ROM rather than RAM. Unfortunately, those features have not yet been standardized in Standard C. (SDCC uses the word "at". Some other compilers use the word "__at" or "_at_". Some other compilers use the symbol "@". GCC apparently(?) uses "__attribute__((section (".theNameOfMyArraySection")))" and also requires tweaking the linker script. You'll have to figure out which approach your particular compiler supports, and then change it if you ever switch compilers).

#include <genesis.h>
#include "snake.h"

#define snake_address 0x7F234

const u32 _at_ snake_address snake_patterns[] = {
    0x00024666,
    // ... some 100ths of lines
};

const u16 temp[] = {
    1, 0x9370, 0x9400,
    ((const u32) snake_address) & 0xFF,
    (((const u32) snake_address) >> 8) & 0xFF,
    (((const u32) snake_address) >> 16) & 0xFF
};
David Cary
  • 5,250
  • 6
  • 53
  • 66
1

After reading the other answers and your comments, I would say that you cannot do it with a normal C build chain. Nate Eldredge's answer is clear about that.

If you need to be able to store this in ROM, I would use the following trick:

Declare your arrays that way:

const int my_array[] = {
    // data...
};
const char my_other_array[] = {
    00, 2, 01, 1, 02, 0
};

Fully build your executable, asking the compiler and linker to generate a full symbol map.

Find the address of my_array in the map, and put it by hand into my_other_array.

Build again the executable with a full map, and make sure that the address has not changed (it should not)

And... note the trick in documentation and in code in red flashing font in case of future maintenance...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • That's likely what I'll do. Thanks for the advice for the red flashing comment (how do you make a red flashing comment in C ? :) ) – T. Tournesol Mar 23 '16 at 16:59
0

I'm afraid you there is no way you can do this.

This is because the address of snake_patterns[] is determined at runtime, that is, after program startup. However, a variable with static storage duration must be initialised "before program startup" (quoted from N1570).

Suppose the address of snake_patterns[] is determined after compilation, what if one copy the executable file and launch them simultaneously, and what if that fixed address is currently occupied by another program? The program won't run successfully in either case. As a result, you have to re-compile your code every time you need them to be executed if you want to know the address at compile time.

Why not simply malloc() some memory to your temp[] at runtime?

nalzok
  • 14,965
  • 21
  • 72
  • 139
  • Why would the address of snake_patterns[] be determined at runtime, if I declared it as a const ? The symbol table shows it's calculated during compilation, and it's stored in the ROM segment... – T. Tournesol Mar 23 '16 at 16:22
  • @T.Tournesol Suppose the address of snake_patterns[] is determined after compilation, what if you copy the executable file and launch them simultaneously, and what if that fixed address is occupied by another program? As a result, you have to re-compile your code every time you need them to be executed! – nalzok Mar 23 '16 at 16:28
  • On the patform I develop for, it can't happen. – T. Tournesol Mar 23 '16 at 16:53
  • @T.Tournesol But C isn't designed for the platform you develop for only, so the C standard had to compromise. – nalzok Mar 23 '16 at 16:56
  • Of course ! But I thought const would prevent this kind of behaviour. For example, my symbol table shows that snake_patterns is located in ROM, so it won't likely be moved at runtime... – T. Tournesol Mar 23 '16 at 16:58