我有这可能是声明的问题:
我声明了一个常量整数数组:
const int my_array[] = {
// data...
}
然后我需要声明另一个形式的字节数组:
00 aa 01 bb 02 cc
其中aabbcc是内存const int的24位地址(我精确地为一个非常特殊的平台编码,这解释了这一点(,所以我写道:
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
};
您会注意到事情有点复杂,但我认为前面的基本示例(用适当的括号固定(以更清晰的方式显示了问题。有些人可能会认出创世纪VDP的DMA调用列表。
用于初始化数组的元素必须是常量表达式。 这些在 C99 标准的第 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。 如果是这样,那么我想我最后的建议是你唯一的希望。 您可能想发布另一个关于如何/是否可以使用您正在使用的特定工具链执行此操作的问题。
"我做错了什么?">
我看到的最直接的事情是....
这不是一个数组:
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[]
内容的头文件。
编辑:
当平台的字节序匹配时(链接器以 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,
};
听起来您希望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
};
- 如何将变量放置在内存中的给定绝对地址(使用 GCC(
- 大多数嵌入式 C 编译器如何定义内存映射 I/O 的符号?
- "使用 gcc 在绝对地址定义变量">
- "使用 C805 进行嵌入式系统设计"第 5.10.6 节"绝对变量位置"比较了 3 种不同的编译器。
在阅读了其他答案和您的评论后,我想说您无法使用普通的 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
。
使用完整映射再次构建可执行文件,并确保地址未更改(不应更改(
并且......请注意文档和代码中的红色闪烁字体中的技巧,以防将来维护...
怕你没有办法做到这一点。
这是因为snake_patterns[]
的地址是在运行时确定的,即程序启动后。但是,具有静态存储持续时间的变量必须在"程序启动之前"初始化(引自 N1570(。
假设snake_patterns[]
的地址是在编译后确定的,如果一个复制可执行文件并同时启动它们,如果该固定地址当前被另一个程序占用怎么办?在这两种情况下,程序都不会成功运行。因此,如果您想在编译时知道地址,则必须在每次需要执行代码时重新编译代码。
为什么不在运行时简单地向temp[]
malloc()
一些内存?