提问人:alx - recommends codidact 提问时间:12/13/2022 更新时间:12/16/2022 访问量:928
使用零大小的非静态数组函数参数是否有效?
Is it valid to use a zero-sized non-static array function parameter?
问:
根据 ISO C(任何版本),指定零大小的数组参数是否有效?
这个标准似乎模棱两可。虽然很明显零大小的数组是无效的,但数组函数参数很特殊:
C23::6.7.6.3/6:
将参数声明为“类型数组”应调整为 “限定的指向类型的指针”,其中类型限定符(如果有)是 在数组类型派生的 [ 和 ] 中指定的那些。如果 关键字 static 也出现在数组类型的 [ 和 ] 中 推导,则对于对函数的每次调用,值 相应的实际参数应提供对第一个 元素,其元素数量至少与 size 表达式。
只要不使用 ,指定的 size 就会被有效地忽略。据我了解引用的段落,编译器根本不允许对指针做出任何假设。static
[]
所以,下面的代码应该是符合的,对吧?
void h(char *start, char past_end[0]);
#define size 100
void j(void)
{
char dst[size];
h(dst, dst+size);
}
我用作指向 one-past-the-end 的哨兵指针(而不是大小;在某些情况下它更舒适)。清楚地告诉人们,这是一个过去的结束,而不是实际的结束,作为一个指针,读者可能会感到困惑。为了清楚起见,结尾将标记为 。past_end[0]
[0]
end[1]
GCC认为它不符合:
$ gcc -Wall -Wextra -Wpedantic -pedantic-errors -std=c17 -S ap.c
ap.c:1:26: error: ISO C forbids zero-size array ‘past_end’ [-Wpedantic]
1 | void h(char *start, char past_end[0]);
| ^~~
Clang似乎同意:
$ clang -Wall -Wextra -Wpedantic -pedantic-errors -std=c17 -S ap.c
ap.c:1:30: warning: zero size arrays are an extension [-Wzero-length-array]
void h(char *start, char past_end[0]);
^
1 warning generated.
如果我不要求严格的 ISO C,GCC 仍然会发出警告(不同),而 Clang 会放松:
$ cc -Wall -Wextra -S ap.c
ap.c: In function ‘j’:
ap.c:7:9: warning: ‘h’ accessing 1 byte in a region of size 0 [-Wstringop-overflow=]
7 | h(dst, dst+size);
| ^~~~~~~~~~~~~~~~
ap.c:7:9: note: referencing argument 2 of type ‘char[0]’
ap.c:1:6: note: in a call to function ‘h’
1 | void h(char *start, char past_end[0]);
| ^
ap.c:7:9: warning: ‘h’ accessing 1 byte in a region of size 0 [-Wstringop-overflow=]
7 | h(dst, dst+size);
| ^~~~~~~~~~~~~~~~
ap.c:7:9: note: referencing argument 2 of type ‘char[0]’
ap.c:1:6: note: in a call to function ‘h’
1 | void h(char *start, char past_end[0]);
| ^
$ clang -Wall -Wextra -S ap.c
我向 GCC 报告了此事,但似乎存在分歧:
https://gcc.gnu.org/pipermail/gcc/2022-December/240277.html
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108036
是否有任何要求来遵守与阵列相同的要求?
此外,如果这被证明是真的,那么以下情况呢?
void f(size_t sz, char arr[sz]);
在严格的 ISO C 模式下,编译器是否有权假设数组始终至少有一个元素,即使我没有使用 ,只是因为我使用了数组语法?如果是这样,那可能是语言的倒退。static
答:
根据 ISO C(任何版本),指定零大小的数组参数是否有效?
C标准很明确。C 2018 6.7.6.2 1,这是一个约束段落,说:
除了可选的类型限定符和关键字之外,and 还可以分隔表达式或 。如果它们分隔表达式(指定数组的大小),则表达式应具有整数类型。如果表达式是常量表达式,则其值应大于零...
static
[
]
*
由于它是一个约束,因此编译器必须发出有关它的诊断消息。但是,与 C 中的大多数内容一样,编译器无论如何都可能接受它。
数组参数对指针的调整无关紧要;这必须稍后进行,因为在有要调整的声明之前,无法调整指向指针的参数声明。因此,必须对声明进行解析,并且它受制于该声明的约束。
清楚地告诉人们,这是一个过去的结束,而不是实际的结束,作为一个指针,读者可能会感到困惑。
[0]
你可以用它来告诉人类读者,但它对编译器来说并不意味着这一点。 告诉编译器至少有元素,甚至比这更没有意义。将子数组传递给函数是有效的,即将指针传递到数组的中间,该函数仅用于访问数组的子集,该子集既不到达原始数组的开头,也不到达原始数组的末尾,因此,即使被接受,也不一定意味着指针指向数组的末尾。指向数组中的任何位置都是有效的。[static n]
n
[n]
[0]
评论
[0]
这是一个有趣的极端情况。
C11 标准的第 6.7.6.2p1 节指定了数组声明符的约束,指出:
除了可选的类型限定符和关键字之外,and 还可以分隔表达式或 。如果它们分隔表达式 (指定数组的大小),表达式应具有 整数类型。如果表达式是常量表达式,则应 值大于零。元素类型不得为 不完整或函数类型。可选类型限定符和 关键字只能出现在函数的声明中 参数,然后仅在最外层的数组中 类型派生。
static
[
]
*
static
您显示的内容构成约束冲突,需要警告/错误,并被视为未定义的行为。但与此同时,由于您有一个数组声明为函数的参数,因此该数组会根据您所说的段落调整为指针类型。
严格来说,编译器显示的内容是正确的,实际上,为了成为严格合规的实现,必须这样做,但是我很难说编译器拒绝具有作为函数参数的程序是有意义的,当它等价于 .char past_end[0]
char *past_end
评论
int x[][]
int x[][0]
int (*)[0]
int x[][0]
int x[0]
int *
ptr1-ptr2
ptr1+integer
ptr1
char padding[sizeof (blob) - 64];
关于 C89 标准(在查看后续版本时也很重要)需要了解的一件重要事情是,它涉及编译编写者之间的妥协,前者不想改变现有编译器的行为,从而拒绝某些构造,而后者使用其他编译器接受这些构造的程序员,他们不想更改他们的代码。
在许多此类情况下,达成的折衷方案是,标准将施加一个约束,要求符合要求的编译器发布诊断,但其客户认为该约束是愚蠢的编译器可以在发布诊断后自由接受代码。如果程序员对他们的代码是“符合”而不是“严格符合”感到满意,那么如果他们和编译器的作者都认为这是愚蠢的,他们就可以继续忽略约束。
启用有关零大小数组的警告的标志被命名为“-pedantic”是有原因的。gcc 的作者认识到,没有约束的语言会更好,但他们提供了一个选项,可以在违反时输出诊断,以满足学究所要求的约束。
评论
start
end
end
endptr
strtol
iterator
end
for(const type* i = start; i != end; i++)
end
I use past_end[0] as a sentinel pointer to one-past-the-end (instead of a size; it's much more comfortable in some cases).
什么?这让我感到困惑。将具有大小的数组传递给函数是非常习惯的。end
end
end
past_end