【问题标题】:Interpretation of an 'Empty' C Array (int a = {};)'空' C 数组的解释(int a = {};)
【发布时间】:2018-08-09 16:52:25
【问题描述】:

我有一段代码定义(我认为是)一个空数组,即一个不包含元素的数组:

int a[] = {};

我用gcc编译sn-p没有问题

一位试图在 MSVS 下编译相同代码的同事进行了修改:

int* a = NULL;

不,他显然认为这是 MSVS 编译器可以接受的等效语句。

但是,稍后在代码中我检索到编号。数组中的元素使用以下宏:

#define sizearray(a)  (sizeof(a) / sizeof((a)[0]))

这样做时:

sizearray({}) returns 0

正如我所期望的那样,我认为这是一个空数组的定义

sizearray(NULL) returns 1 

我认为sizeof(NULL)/sizeof((NULL)[0])) 实际上是4/4 == 1

作为NULL == (void*)0

我的问题是:

int a[] = {}; 

是表达空数组的有效方式,还是它的编程习惯不好。

另外,您是否不能在 MSVS 编译器中使用这样的表达式,即这是某种 C99 兼容性问题?

更新:

刚刚编译了这个:

#include <stdio.h>

#define sizearray(a)  (sizeof(a) / sizeof((a)[0]))

int main()
{
    int a[] = {};
    int b[] = {0};
    int c[] = {0,1};

    printf("sizearray a = %lu\n", sizearray(a));
    printf("sizearray b = %lu\n", sizearray(b));
    printf("sizearray c = %lu\n", sizearray(c));

    return 0;
}

使用这个 Makefile:

array: array.c
    gcc -g -o array array.c

我的编译器是:

gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) 

编译没有任何问题,输出如下:

bph@marvin:~/projects/scratch/c/array$ ./array
sizearray a = 0
sizearray b = 1
sizearray c = 2

很好奇?会不会偷偷是C++编译器,而不是C编译器?

尝试了 John Bodes 对其他编译器标志的建议,并且可以确认编译确实失败了:

gcc --std=c11 --pedantic -Wall -g -o array array.c
array.c: In function ‘main’:
array.c:7:15: warning: ISO C forbids empty initializer braces [-Wpedantic]
     int a[] = {};
               ^
array.c:7:9: error: zero or negative size array ‘a’
     int a[] = {};
         ^
Makefile:2: recipe for target 'array' failed
make: *** [array] Error 1

【问题讨论】:

  • 不 - 让我仔细检查一下!
  • 很有可能..
  • 这里没有数组。
  • 将标志 --std=c11 --pedantic --Wall 添加到您的 gcc 命令中,您会收到警告。
  • @bph - -Wall -Werror 应该在 IMO 的每个构建命令中 - 它会打开所有警告,并使所有警告错误,这意味着您必须在代码将构建。这并不总是现实的(一些旧的 API 会在较新的编译器中触发警告),但它至少应该是一个起点。 --pedantic 将拒绝大多数(不是全部!)编译器扩展,提供最大的可移植性。但是,有时您需要这些扩展。

标签: c arrays visual-studio gcc


【解决方案1】:

空初始化器在 C 中是无效的。所以

int a = {};

格式不正确。见6.7.9 Initialization

sizearray(NULL) 也无效。因为sizearray 宏会扩展为:

sizeof 0 /sizeof 0[0])

如果NULL 定义为0。这是无效的,因为 0[0] 无效,因为不涉及指针或数组(根据指针运算的要求 - 请记住 a[b] 等效于 *(a + b))。

或者,它会扩展为:

(sizeof(((void *)0)) / sizeof((((void *)0))[0]))

如果 NULL((void*)0) 相同。这是无效的,因为在 void 指针上不允许指针算术。请参阅 6.5.6, 2void* 是不完整的类型。无论NULL 的定义在实现中如何,都存在类似的问题(C 标准对空指针常量的定义很灵活,即NULL。参见7.19, 3)。

因此,在这两种情况下,您看到的是非标准代码的编译器特定行为。

【讨论】:

  • 啊 - 非常抱歉,但我在 OP 中有错字,应该说 int a[] = {};
  • 幸运的是,答案没有改变。 int a[] = {}; 也无效。 {} 在 C 中通常被称为“通用初始化程序”(即,您可以使用它初始化任何类型的对象)。您也可以使用它来初始化缩放器(例如,int a = {1}; 是有效的)。但是,无论您正在初始化什么对象,都不允许使用空的初始化程序。所以int a[] = {};int a = {}; 一样无效。
  • 我认为很明显我不应该使用它,即使我的编译器偶然似乎做了我所期望的,即将它视为一个空数组(长度为 0)。按照 JohnBode 的建议应用额外的编译器标志确实会导致构建失败(请参阅 OP 中的更新)
【解决方案2】:

这不是一个数组,但也不是一个标量:这是一个语法错误。

C11 草案在 §6.7.9.11(初始化语义)中说:

标量的初始值设定项应为单个表达式,可选 括在大括号中。对象的初始值是 表达式(转换后);相同的类型约束和转换 对于简单的赋值应用,将标量的类型作为 其声明类型的非限定版本。

但大括号之间必须有东西,不能为空。

所以我认为这个问题缺少一些东西,这不是实际的代码。

【讨论】:

  • 这个答案也蛮有用的->stackoverflow.com/questions/201101/…
  • 你看到 OP 更新了吗?我真的很惊讶它编译时牢记所有这些答案表明它不应该
  • 语法错误可以编译吗?即语法错误是否属于“非标准代码的编译器特定行为”类别并且仍然可以编译?
【解决方案3】:

不是数组,是大括号初始化语法。

简而言之,你可以这样写:

int a = {1234};

它不会用数组初始化a,它只是分配1234。如果有2个或更多值,那就是错误。

大括号初始化禁用值截断,所以:

char b = 258; // Valid, same as b = 2
char b = {258}; // Wrong, can't truncate value in braces

空括号只是零初始化器,所以int a = {} 等价于int a = {0}

【讨论】:

  • 什么是截断值?
  • 标准 C 中的空括号是语法错误。 GCC 和 C++ 有不同的解释。我认为您关于值截断的 cmets 也不适用于 C。
  • @bph 258 对于一个字节(0-255)来说太大了,所以较高的字节将被截断,即0x102 -> 0x2
  • @iBug 我看不到标准中截断行为不同的任何内容,我认为这不是真的。
  • @PaulOgilvie “iBug,你不能说” 为什么不呢? {1234} 不是数组。
猜你喜欢
  • 2011-01-16
  • 1970-01-01
  • 2016-04-13
  • 1970-01-01
  • 1970-01-01
  • 2012-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多