声明一系列常量

时间:2016-03-23 15:35:45

标签: c arrays const

我认为这可能是申报问题:

我声明了一个const int数组:

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

然后我需要声明另一个形式的字节数组:

00 aa 01 bb 02 cc

其中aabbcc是const int内存中的24位地址(我为一个非常特殊的平台编写了精确的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
};

您会注意到事情有点复杂,但我认为之前的基本示例(使用适当的括号修复)会以更清晰的方式显示问题。有些人可能会识别Genesis VDP的DMA调用列表。

6 个答案:

答案 0 :(得分:7)

用于初始化数组的元素需要是常量表达式。这些在the C99 standard的第6.6节或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已经填写,并且您的目标平台是您知道程序将在内存中的位置,那么您可以使用汇编程序或链接程序的功能,以实现您的需要。但当然这将是系统特定的。

(编辑:你在另一条评论中已经提到过这个阵列需要进入ROM。如果是这样,那么我认为我的最后一个建议是你唯一的希望。你可能想发布另一个关于如何/是否可以做的问题这与你正在使用的特定工具链。)

答案 1 :(得分:6)

&#34;我做错了什么?&#34;

我看到的最直接的事情是......

这不是数组:

const int my_array = { /* elements */ }

这是一个数组:

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

此外,请勿忽略该错误消息!它说实话!

error: initializer element is not constant

您正在使用非常量的东西,即&#34; my_array&#34;来初始化数组元素。 &#34; my_array&#34;将评估指向数组的第一个元素的指针,并且在编译时不知道该值。

答案 2 :(得分:3)

假设您要评估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[]内容的头文件。

编辑:

当您的平台的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,
};

答案 3 :(得分:1)

在阅读了其他答案和您的评论之后,我会说你不能用正常的C构建链做到这一点。 Nate Eldredge的答案很清楚。

如果你需要能够将它存储在ROM中,我会使用以下技巧:

以这种方式声明你的数组:

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

完全构建可执行文件,要求编译器和链接器生成完整的符号映射。

在地图中找到my_array的地址,然后手动将其放入my_other_array

使用完整地图再次构建可执行文件,并确保地址未更改(不应该更改)

并且......在未来维护的情况下,请注意红色闪烁字体代码中的文档中的技巧...

答案 4 :(得分:1)

听起来你想在ROM中存储一个“变量”来存储其他变量的地址。

许多平台的RAM都很少,因此在(程序存储器)ROM中放入尽可能多的数据而不是稀缺的RAM是一个好主意。

使用标准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中。 不幸的是,这些功能尚未在标准C中标准化。 (SDCC使用“at”一词。其他一些编译器使用“__at”或“_at_”一词。其他一些编译器使用符号“@”。GCC显然(?)使用“__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
};

答案 5 :(得分:0)

我担心你无法做到这一点。

这是因为snake_patterns[]的地址是在运行时确定的,即在程序启动后。但是,具有静态存储持续时间的变量必须在程序启动之前初始化&#34; &#34; (引自N1570)。

假设编译后确定snake_patterns[]的地址,如果复制可执行文件并同时启动它们会怎样,如果该固定地址当前被另一个程序占用该怎么办?在任何一种情况下,该计划都不会成功运行。因此,如果您想在编译时知道地址,则必须在每次需要执行代码时重新编译代码。

为什么不在运行时malloc()简单temp[]一些内存?