使用零大小的非静态数组函数参数是否有效?

Cac*_*ito 11 c arrays pointers function-parameter language-lawyer

根据 ISO C(任何版本),指定零大小的数组参数是否有效?

\n

该标准似乎含糊不清。虽然很明显零大小的数组是无效的,但数组函数参数很特殊:

\n

C23::6.7.6.3/6:

\n
\n

将参数声明为“类型数组”应调整为“指向类型的限定指针”,其中类型限定符(如果有)是在数组类型派生的 [ 和 ] 内指定的限定符。如果\n关键字 static 也出现在数组类型派生的 [ 和 ] 中,则对于每次调用该函数,\n相应实际参数的值应提供对数组的第一个\n元素的访问,\n至少为由 size 表达式指定的许多元素。\n

\n
\n

只要您不使用static,之间指定的大小[]就会被有效忽略。据我了解引用的段落,编译器根本不允许对指针做出任何假设。

\n

那么,下面的代码应该是符合要求的,对吧?

\n
void h(char *start, char past_end[0]);\n\n#define size 100\nvoid j(void)\n{\n        char dst[size];\n        h(dst, dst+size);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我使用past_end[0]作为指向末尾一位的哨兵指针(而不是大小;在某些情况下它更舒服)。清楚[0]地表明这是一个过去的结束,而不是实际的结束,作为一个指针,读者可能会感到困惑。结束将被标记为end[1]为清楚起见,

\n

GCC 认为它不符合:

\n
$ gcc -Wall -Wextra -Wpedantic -pedantic-errors -std=c17 -S ap.c \nap.c:1:26: error: ISO C forbids zero-size array \xe2\x80\x98past_end\xe2\x80\x99 [-Wpedantic]\n    1 | void h(char *start, char past_end[0]);\n      |                          ^~~\n
Run Code Online (Sandbox Code Playgroud)\n

Clang似乎同意:

\n
$ clang -Wall -Wextra -Wpedantic -pedantic-errors -std=c17 -S ap.c \nap.c:1:30: warning: zero size arrays are an extension [-Wzero-length-array]\nvoid h(char *start, char past_end[0]);\n                                  ^\n1 warning generated.\n
Run Code Online (Sandbox Code Playgroud)\n

如果我不要求严格的 ISO C,GCC 仍然会发出警告(不同),而 Clang 则放松:

\n
$ cc -Wall -Wextra -S ap.c \nap.c: In function \xe2\x80\x98j\xe2\x80\x99:\nap.c:7:9: warning: \xe2\x80\x98h\xe2\x80\x99 accessing 1 byte in a region of size 0 [-Wstringop-overflow=]\n    7 |         h(dst, dst+size);\n      |         ^~~~~~~~~~~~~~~~\nap.c:7:9: note: referencing argument 2 of type \xe2\x80\x98char[0]\xe2\x80\x99\nap.c:1:6: note: in a call to function \xe2\x80\x98h\xe2\x80\x99\n    1 | void h(char *start, char past_end[0]);\n      |      ^\nap.c:7:9: warning: \xe2\x80\x98h\xe2\x80\x99 accessing 1 byte in a region of size 0 [-Wstringop-overflow=]\n    7 |         h(dst, dst+size);\n      |         ^~~~~~~~~~~~~~~~\nap.c:7:9: note: referencing argument 2 of type \xe2\x80\x98char[0]\xe2\x80\x99\nap.c:1:6: note: in a call to function \xe2\x80\x98h\xe2\x80\x99\n    1 | void h(char *start, char past_end[0]);\n      |      ^\n
Run Code Online (Sandbox Code Playgroud)\n
$ clang -Wall -Wextra -S ap.c \n
Run Code Online (Sandbox Code Playgroud)\n

我向 GCC 报告了这一点,似乎存在分歧:

\n

https://gcc.gnu.org/pipermail/gcc/2022-December/240277.html

\n

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108036

\n

是否有任何要求需要遵守与数组相同的要求?

\n

此外,如果事实证明这是真的,那么以下情况又如何呢?

\n
void f(size_t sz, char arr[sz]);\n
Run Code Online (Sandbox Code Playgroud)\n

在严格的 ISO C 模式下,编译器是否有权假设数组始终至少有一个元素,即使我没有使用static,只是因为我使用了数组语法?如果是这样,那可能是语言的倒退。

\n

Eri*_*hil 17

\n

根据 ISO C(任何版本),指定零大小的数组参数是否有效?

\n
\n

C标准很明确。C 2018 6.7.6.2 1,这是一个约束段落,表示:

\n
\n

除了可选的类型限定符和关键字之外static[and 还]可以分隔表达式 或*。如果它们分隔表达式(指定数组的大小),则该表达式应具有整数类型。如果表达式是常量表达式,则其值应大于零\xe2\x80\xa6

\n
\n

由于它是一个约束,编译器必须发出有关它的诊断消息。然而,与 C 中的大多数内容一样,编译器无论如何都可以接受它。

\n

数组参数与指针的调整是无关的;这必须稍后进行,因为在有需要调整的声明之前,无法调整指针的参数声明。因此必须对声明进行解析,并且它受到其约束。

\n
\n

清楚[0]地表明它已经过了终点,而不是实际的终点,作为一个指针,读者可能会感到困惑。

\n
\n

您可以用它来告诉人类读者这一点,但对于编译器来说并不意味着这一点。告诉编译器至少有元素,并且意义甚至更小。将子数组传递给函数\xe2\x80\x94将指针传递到数组的中间是有效的,该函数仅用于访问数组的子集,既不到达数组的开头也不到达结尾原始数组,因此,即使被接受,也不一定意味着指针指向数组的末尾。指向数组中的任何位置都是有效的。[static n]n[n][0]

\n


dbu*_*ush 6

这是一个有趣的极端案例。

C11标准第 6.7.6.2p1 节指定了数组声明符的约束:

除了可选的类型限定符和关键字之外static[ and 还]可以分隔表达式 或*。如果它们分隔表达式(指定数组的大小),则该表达式应具有整数类型。如果表达式是常量表达式,则其值应大于零。元素类型不应是不完整类型或函数类型。可选类型限定符和关键字static只能出现在具有数组类型的函数参数的声明中,并且只能出现在最外层的数组类型派生中。

您所显示的内容构成了约束违规,需要警告/错误并被视为未定义的行为。但与此同时,因为您有一个数组被声明为函数的参数,所以该数组会根据您所说的段落调整为指针类型。

严格来说,编译器显示的内容是正确的,事实上,为了成为严格一致的实现,需要这样做,但是我很难说编译器拒绝具有以下内容的程序是有意义的:char past_end[0]当它相当于 时作为函数参数char *past_end