在C语言中,在文件范围内使用可变修改的数组。

71
我有一些类似这样的代码:
static int a = 6;
static int b = 3;

static int Hello[a][b] =
{
    { 1,2,3},
    { 1,2,3},
    { 1,2,3},
    { 1,2,3},
    { 1,2,3},
    { 1,2,3}
};

但是当我编译它时,它报错:

在文件范围内可变地修改的 'Hello'

这是怎么发生的?我该如何修复它?


2
可能是 文件作用域下的可变数组 的重复问题。 - tstew
1
@tstew:不,那是用于Objective-C的。 - Peter Mortensen
1
@PeterMortensen(和其他人)- 我已经将一些问题标记为此问题的重复(我认为它们应该全部显示在右侧列中的链接中)。如果你能浏览它们并对我正确的那些问题进行关闭投票,那就太好了。 - Toby Speight
注意:标题部分是字面意思,而“变量”是(字面上的)错误信息的一部分。例如,"variably modified ‘child’ at file scope"。 - Peter Mortensen
显示剩余2条评论
5个回答

96
你不能拥有一个大小由变量给出的静态数组。
这就是为什么常量应该使用 #define 定义的原因。
#define a 6

这样,预处理器将会用6替换a,使其成为一个有效的声明。

将int a = 6; int b = 3 定义为非静态变量而不是static int a = 6有何影响? - user707549
3
不,这仍然将是一个变量。使用 #define。在C++中有 const,可以允许 const int a = 6; 正常工作,但即使是 const 在C中也不足够。 - zch
6
一种替代宏的方法是使用匿名枚举,它们是真正的整数常量。 enum { a = 6, b = 3, }; - tstanisl

11
简单回答:在文件范围内无法修改变量的数组是不可能的。
详细解释:
将其设置为编译时的整数常量表达式,因为数组长度必须在编译时指定。
像这样:
#define a 6
#define b 3

或者,遵循C99标准,并像GCC一样进行编译。

gcc -Wall -std=c99 test.c -o test.out

这里的问题是具有提供长度的可变长度数组可能未初始化,因此您会收到此错误。

简单地说

static int a = 6;
static int b = 3;

void any_func()
{
    int Hello [a][b]; // No need of initialization. No static array means no file scope.
}

现在使用一个for循环或任何循环来填充数组。
更多信息,请参考一个演示:
#include <stdio.h>

static int a = 6;
int main()
{
    int Hello[a] = {1, 2, 3, 4, 5, 6}; // See here initialization of the array 'Hello'. It's in the function
                                       // Scope, but still an error
    return 0;
}

编译

cd ~/c
clang -std=c99 vararr.c -o vararr

输出:

vararr.c:8:11: error: variable-sized object may not be initialized
int Hello[a]={1,2,3,4,5,6};
          ^
1 error generated.

如果你移除静态修饰符并提供初始化,那么会产生上述错误。
但是如果你保留静态修饰符和初始化,仍然会出现错误。
但是如果你移除初始化并保留 "static" 关键字,将会出现下面的错误。
error: variable length array declaration not allowed at file scope
static int Hello[a];
           ^     ~
1 error generated.

在文件范围内不允许使用可变长度数组声明,因此可以将其放在任何函数的函数范围或块范围内(但要记住,将其放在函数范围内必须去除初始化)

注意:由于标记为C,将ab声明为const对你没有帮助,但在C++中,const将正常工作。


2
C99在文件作用域也不支持VLA,它必须在函数作用域或更小的范围内使用。他可以在文件作用域使用const索引声明,例如static const int a = 10; - WhozCraig
@Omkant,您可以添加另一种可在此处使用的“积分常量表达式”即枚举常量。 - Jens Gustedt
让我困惑的是为什么 static size_t const size 在文件作用域下不能用于数组大小。它在函数作用域下即使在 C89 中也可以工作,因此我希望一个在编译和加载时已知且为常量的对象能够在那里使用。除非 static 是问题所在,这很糟糕,因为我想将它们绑定到一个编译模块。 - Braden Best
实际上,只有当数组是 static uint8_t[] 时才会发生这种情况。编译器在处理 static char[] 时没有问题。但是,当我尝试设置一个 repl.it 来演示这个问题时(请注意,这是在匿名的 repl.it 中已经演示了错误,并将代码从那个 repl.it 复制/粘贴到了一个命名的 repl.it 中),它就可以编译而没有问题。 - Braden Best
我在用C语言编写Brainfuck虚拟机时遇到了这个问题,当我创建物理内存并为程序计数器和虚拟内存分配指针时。 - Braden Best
显示剩余7条评论

4
使用Clang/LLVM时,以下内容有效:
static const int a = 6;
static const int b = 3;

static int Hello[a][b] =
{
    {1, 2, 3},
    {1, 2, 3},
    {1, 2, 3},
    {1, 2, 3},
    {1, 2, 3},
    {1, 2, 3}
};

(要在生成的汇编代码中看到它,需要使用 'Hello' ,这样它就不会被优化掉。)

然而,如果选择了C99模式(-std=c99),这将生成一个错误。只有当选择了-pedantic时,它才会生成一个警告(Wgnu-folding-constant)。

=== 编辑 === 现在的Clang(版本14.0.0)默认生成此警告: 警告:可变长度数组被折叠为常量数组作为一种扩展[-Wgnu-folding-constant]

GCC似乎不允许这样做(const被解释为只读)。

请参阅此问题的解释:

"Initializer element is not constant" error for no reason in Linux GCC, compiling C


3
在C语言中,“const”关键字并不真正意味着“常量”。这会误导一些用户。 - Low power
@Lowpower 我同意 - 这是实现的细节。此外,成本的实际实现似乎有所不同,在C++中至少一些const不再是“只读变量(这意味着符号表中没有指向只读内存段的符号),而是在编译时解析的真正常数。 - PolarBear2015
1
既不是clang也不是gcc允许这个代码“干净地”编译,因为它是无效的C代码,并且还包含非标准的编译器扩展。需要注意的是,一个程序可能只有警告而仍然是无效的C代码。 - Lundin
1
@Lundin 自从我写下这个答案以来,显然发生了变化(这是好事),现在Clang默认生成警告:“可变长度数组折叠为常量数组作为扩展”(-Wgnu-folding-constant)。代码仍然“有效” - 如果这意味着被编译器接受并按预期工作(就像所有扩展一样),但不符合标准。 - PolarBear2015
1
如果某个东西违反了标准中的约束或语法规则,那么它就是无效的C语言 - 它不再是C语言,而是"带有扩展的C语言"。编译器的扩展可能经常违反约束/语法,但这样做会使编译器不符合规范,除非它发出警告。 - Lundin

1

是的,这很烦人:

const int len = 10;

int stuff[len];

出现错误。我试图避免使用#define x,因为const int是声明常量的更好方式,但是有些情况下,即使编译器完全知道const int是常量,它也不是真正的常量。


0
数组的维度必须是常量表达式,并且你的好朋友“编译器”必须知道这一点。所以告诉编译器a和b是常量值。
static constexpr int a = 6;

static constexpr int b = 3;

static int Hello[a][b] = { { 1,2,3 }, { 1,2,3 }, { 1,2,3 }, { 1,2,3 }, { 1,2,3 }, { 1,2,3 } };

在旧版的C/C++中,使用'const'关键字。 - Boris Radonic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接