Объявление массива констант

у меня это может быть проблемой объявления:

объявляю массив const int:

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

тогда мне нужно объявить другой массив байтов формы:

00 aa 01 bb 02 cc

где aabbcc-24-битный адрес в памяти const int (я уточняю код I для очень конкретной платформы, это объясняет это), поэтому я написал :

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

но я получаю эту ошибку :

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

Я думал о кастинге 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
}

но затем я получаю предупреждение + ошибка :

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

что я делаю не так ?

вот фактический код, для тех, кто спрашивает (Я вырезал ненужные части):

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

вы заметите, что все немного сложнее, но я думаю, что предыдущий основной пример (исправленный с соответствующими скобками) показывает проблему более ясным образом. Некоторые могут распознать список вызовов DMA для VDP Genesis.

6 ответов


элементы, которые вы используете для инициализации массивов должны быть константные выражения. Они определены в разделе 6.6 стандарт C99 или же C11. См. пункт 7:

больше широты разрешено для постоянных выражений в инициализаторах. Такое постоянное выражение должно быть одним из следующий:

  • арифметическое постоянное выражение,
  • константа нулевого указателя,
  • адрес постоянного или
  • константа адреса для типа объекта плюс или минус целое выражение константы.

теперь my_array является константой адреса, но все, что вам разрешено делать, это добавлять или вычитать целочисленную константу. Сдвигая или маскируя, вы создаете что-то, что больше не является постоянным выражением, поэтому недопустимо в инициализаторе.

Я думаю, обоснование этого ограничения заключается в том, что C предназначен для использования для перемещаемого кода, где местоположение программы в памяти может быть неизвестно, пока она не будет загружена при подготовке к выполнению. В такой системе ссылки на адреса внутри программы должны быть заполнены загрузчиком на основе таблицы внутри двоичного файла, который он читает во время загрузки (например, "при относительном адресе 0x12345678 внутри программы заполните абсолютный адрес объекта my_array Как известно"). Эта таблица обычно имеет довольно ограничительный формат и, вероятно, имеет способ выражения постоянных смещений ("заполните абсолютный адрес объекта my_array, плюс 42"), но обычно не поддерживает произвольную арифметику.

наверное, самое простое решение для вас-сделать my_other_array не будет const и заполните его во время выполнения, написав функцию, которая извлекает необходимые биты адреса my_array и вставляет их в my_other_array, и вызовите эту функцию прежде чем вам нужно использовать my_other_array.

если это важно по какой-то причине my_other_array будьте уже заполнены, когда программа загружается, и ваша целевая платформа такова, что вы знаете, где в памяти будет расположена программа, тогда вы можете использовать возможности вашего ассемблера или компоновщика для достижения того, что вы хотите. Но, конечно, это будет специфично для системы.

(Edit: Вы упомянули в другом комментарии, что этот массив должен идти в ПЗУ. Если да, то я думаю, что мое последнее предложение это твоя единственная надежда. Возможно, вы захотите опубликовать еще один вопрос о том, как / можете ли вы сделать это с помощью конкретной цепочки инструментов.)


самое непосредственное, что я вижу....

это не массив:

const int my_array = { /* elements */ }

это массив:

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

кроме того, не игнорируйте это сообщение об ошибке! он говорит правду!

error: initializer element is not constant

вы используете что-то, что не является константой, а именно "my_array", для инициализации элементов массива. "my_array" будет возвращать указатель на первый элемент массива, и это значение не известно время компиляции.


предполагая, что вы хотите оценить my_array[X] а не my_array:

это не поддерживается C; более короткий пример может быть

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

несмотря на const ключевое слово, стандарт C не позволяет оценить это во время компиляции.

вы можете попробовать поместить его в контекст функции

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

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

    memcpy(bar, tmp, sizeof tmp);
}

кроме того, вы можете использовать внешний инструмент для создания файла заголовка с bar[] содержание.

EDIT:

когда ваш endianess платформы соответствует (компоновщик помещает адрес массива так, как это ожидается контроллером DMA), вы можете попробовать

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

после прочтения других ответов и ваших комментариев, я бы сказал, что вы не можете сделать это с нормальным c построение цепочки. Ответ Нейта Элдриджа ясен.

Если вам нужно иметь возможность хранить это в ПЗУ, я бы использовал следующий трюк:

объявить массивы таким образом:

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

полностью создайте исполняемый файл, попросив компилятор и компоновщик создать полную карту символов.

найдите адрес my_array на карте и поместите его вручную в my_other_array.

снова построить исполняемый файл с полной картой, и убедитесь, что адрес не изменился (он не должен)

и... обратите внимание на трюк в документации и в коде красным мигающим шрифтом в случае будущего обслуживания...


похоже, вы хотите, чтобы одна "переменная" в ПЗУ хранила адрес какой-то другой переменной.

многие платформы имеют очень мало ОЗУ, поэтому положить как можно больше данных в (память программы) ПЗУ, а не скудный ОЗУ хорошая идея.

портативный подход с использованием стандартного C

возможно, использование "контекста функции", как предложил ensc, достаточно близко?

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

нестандартные расширения

несколько компиляторов C имеют несколько функций, которые пусть вы скажете компилятору поместить вещи в ROM, а не в RAM. К сожалению, эти характеристики еще не стандартизированы в стандарте С. (SDCC использует слово"at". Некоторые другие компиляторы используют слово "__at" или "_at_". Некоторые другие компиляторы используют символ"@". ССЗ видимо(?) использует "__attribute__((section (".theNameOfMyArraySection")))", а также требует настройки скрипта линкера. Вам нужно будет выяснить, какой подход поддерживает ваш конкретный компилятор, а затем изменить его, если вы когда-либо переключались компиляторы.)

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

боюсь, что вы не сможете этого сделать.

это потому, что адрес snake_patterns[] определяется во время выполнения, то есть после запуск программы. Однако переменная со статической длительностью хранения должна быть инициализирована "до запуск программы "(цитируется по N1570).

предположим, что адрес snake_patterns[] определяется после компиляции, что, если скопировать исполняемый файл и запустить их одновременно, и что, если это исправлено в настоящее время занят другой программой? Программа не будет работать успешно в любом случае. В результате вы должны повторно компилировать свой код каждый раз, когда вам нужно их выполнить, если вы хотите знать адрес во время компиляции.

Почему бы просто malloc() память в свой temp[] во время выполнения?