4

为什么会int a[5] = {1,2,3,4,5,6}发出警告而不发出警告int a[5] = {1,2,3,4,5}; a[5] = 6;

当我最初将数组大小声明为 5 时,这样做是一个好习惯吗?

如果我不知道数组的大小怎么办?我可以这样声明int a[]吗?

4

4 回答 4

8

为什么 int a[5] = {1,2,3,4,5,6} 给出警告而 int a[5] = {1,2,3,4,5}; 一[5] = 6; 才不是?

赋值给你一个警告,因为你知道初始化语句中变量的大小,它显然违反了你声明的大小。您没有a行中的数组大小a[6] = 6,因此对于编译器来说似乎没问题。当然,警告级别因编译器而异,对于某些编译器,您可以指定额外的警告。

例如,使用 gcc,您可以使用标志-Wextra-Wall获得大量警告。收到警告是一件好事,因为编译器可以帮助您找到可能的警告,而无需调试您的代码。当然,只有当你修复它们时它们才是好的:-)

当我最初将数组大小声明为 5 时,这样做是一个好习惯吗?

将整数分配给您未声明的内存位置绝不是一个好习惯 - 您无法确定该值被写入的位置,它可能会覆盖另一个变量,或者更糟糕的是,部分覆盖其他一些变量或堆栈。由于这种东西因编译器而异,正如@PascalCuoq 所指出的那样,它被称为undefined behavior,并且是您要不惜一切代价避免的事情。当然,由于它是未定义的,因此您的程序可能会在此声明后正常执行,但这是一种非常糟糕的做法。

但是,初始化一个固定大小的数组并没有什么问题,如果它不会改变的话。您应该避免使用幻数,而是使用常量,例如MAX_NUMBER_OF_PERMUTATIONSor CURRENCIES_SIZE

我可以这样声明:int a[]?

int a[]在初始化固定数组时将其声明为简写,编译器可以指定元素的数量。例如:

int a[] = {1,2,3}; //this is good
int b[3] = {1,2,3}; //same from above

过去它通常是声明int a[];,但它不适用于每个编译器,因此应该避免。(感谢@PascalCuoq 指出这一点)

如果我不知道数组的大小怎么办?

如果您不知道数组的大小,则应将其声明为指针,例如使用、和类似的系统调用int * a自己管理内存。请做好工作,也学习一下——世界以后会感谢你的。如果您正在寻找动态内存分配,您应该阅读指针而不是数组。mallocrealloccallocfree

于 2013-04-26T16:58:05.520 回答
6

为什么 int a[5] = {1,2,3,4,5,6} 给出警告而 int a[5] = {1,2,3,4,5}; a[6] = 6; 才不是?

警告只是编译器试图帮助你。编译器不必在您每次做错事时发出警告。编写错误程序的方法太多,编译器无法对所有这些方法发出警告。

当我最初将数组大小声明为 5 时,这样做是一个好习惯吗?

不。当a是一个大小为 5 的数组时,访问a[6]a[5]调用未定义的行为(翻译:这是非常糟糕的)。关于未定义行为的传统说法是它允许编译器使守护进程飞出你的鼻子

在 1992 年初对该组的一次讨论中,一位常客评论道:“当编译器遇到 [给定的未定义构造] 时,它让恶魔飞出你的鼻子是合法的”(暗示编译器可能会选择任意离奇的在不违反 ANSI C 标准的情况下解释代码的方法)。

因此,简而言之,始终避免在 C 程序中出现未定义的行为。

如果我不知道数组的大小怎么办?我可以像这样声明它 int a[] 吗?

不,你不能。声明数组时,您必须知道数组的大小。int a[];将声明一个不完整的数组,这不是您想要的(这里是关于不完整类型的链接,但是如果您询问访问五元素数组的第六个元素,您只是不想听到不完整类型)。如果您不知道最终需要的尺寸,请了解malloc()realloc()

于 2013-04-26T16:59:36.043 回答
2

问题:为什么

int a[5] = {1,2,3,4,5,6};

同时发出警告

 int a[5] = {1,2,3,4,5};
 a[5] = 6;

才不是?

这实际上是一个很好的问题,我没有一个很好的答案。

至于 C 语言标准所说的,初始化是违反约束的,这意味着符合要求的编译器必须发出诊断,并且可能会拒绝该程序。(gcc-pedantic-errors使用我推荐使用的选项来执行此操作)。

在第二种情况下,a[5] = 6;具有未定义的行为。该语言不需要诊断,但它肯定允许诊断,并且足够聪明的编译器可以警告它。当编译器看到对 的赋值时a[5],它知道(或可能知道)a只有 5 个元素并且您正在尝试访问第 6 个元素。gcc,至少没有这样做,至少没有我尝试过的选项。

为什么该标准要求对第一种情况进行诊断,而不对第二种情况进行诊断?因为第一个错误总是可以在编译时检测到。编译器必须知道有多大a,因为声明正在创建它;它必须为它分配内存。在这样的声明中,大小始终在编译时已知。(有可变长度数组,但你不能为它们使用那种初始化器。)

a[5] = 6;中,编译器将不得不执行更多分析来检测错误,而这种分析并不总是可能的。你可以很容易地写:

a[n] = 6;

n如果碰巧是这样,那将同样糟糕5-但是编译器必须在编译时确定运行时n将要诊断它的值。编译器有时可以执行这种分析,但在某些情况下理论上是不可能的。

那么为什么 gcc 不诊断特定情况a[5] = 6;?这可能只是 gcc 的开发人员选择在哪里投入时间和其他资源的问题。

于 2013-04-26T17:39:37.880 回答
2

在您的第二个示例中,编译器正在执行指针数学运算以找出将整数放在哪里。在较旧的 C 实现中,数组不是一等类型,因此没有可用的范围检查。一些较新的可以检测越界错误。

如果您需要动态分配数组,这是正确的模式:

int *a;
// calloc( elements, size)
// create an array of 6 elements, 0..5
a = calloc(6, sizeof(int));
// use like a[5]=6, be sure to free(a) later. 

检查callocfree引用。

于 2013-04-26T17:01:01.900 回答