chq*_*lie 6 c c99 null-pointer language-lawyer c11
当尝试利用C99函数原型语法为函数参数指定非null指针时,我在clang和gcc之间遇到了一些不一致的行为:
可以声明并定义一个函数,以接收指向最小大小的数组的非null指针。例如:
char *mystrcpy(char dest[restrict static 1], char const src[restrict static 1]);
声明函数mystrcpy接受非空的char数组指针。
此定义比strcpy使用更经典形式的标准定义更严格:
char *strcpy(char restrict *dest, const char restrict *src);
如果没有为C编译器提供任何有关约束的信息,则参数为非null。
我编写了一个测试程序来验证这两个原型的兼容性,并且惊讶地发现它们确实兼容,尽管第一个中包含的信息比第二个中包含的信息更多。这些事实进一步令人惊讶:
strcpy接收空参数。strcpy接收了空参数,而不是不接收mystrcpy。strcpy或mystrcpy给使用两种语法定义的函数指针都不会引起任何警告。我的问题是:这些观察结果是否与C标准一致,或者gcc和/或clang在函数参数
static内对C99 关键字的实现不正确[]?
这是代码:
#include <stdio.h>
#include <string.h>
static char *mystrcpy(char dest[restrict static 1], char const src[restrict static 1]) {
char *p = dest;
while ((*p++ = *src++) != '\0')
continue;
return dest;
}
static char *(*f1)(char *dest, const char *src) = strcpy;
static char *(*f2)(char *dest, const char *src) = mystrcpy;
static char *(*f3)(char dest[restrict static 1], char const src[restrict static 1]) = strcpy;
static char *(*f4)(char dest[restrict static 1], char const src[restrict static 1]) = mystrcpy;
int main() {
char a[100];
strcpy(a, "a");
strcpy(a, "");
strcpy(a, NULL);
strcpy(a, a);
strcpy(NULL, a);
strcpy(NULL, NULL);
mystrcpy(a, "a");
mystrcpy(a, "");
mystrcpy(a, NULL);
mystrcpy(a, a);
mystrcpy(NULL, a);
mystrcpy(NULL, NULL);
f1(a, "a");
f1(a, "");
f1(a, NULL);
f1(a, a);
f1(NULL, a);
f1(NULL, NULL);
f2(a, "a");
f2(a, "");
f2(a, NULL);
f2(a, a);
f2(NULL, a);
f2(NULL, NULL);
f3(a, "a");
f3(a, "");
f3(a, NULL);
f3(a, a);
f3(NULL, a);
f3(NULL, NULL);
f4(a, "a");
f4(a, "");
f4(a, NULL);
f4(a, a);
f4(NULL, a);
f4(NULL, NULL);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
gcc输出:它只抱怨strcpywith NULL参数的直接调用。
$ gcc -O2 -std = c99 -Wall -Wextra -W -o sc sc.c sc.c:在函数“ main”中: sc.c:22:5:警告:null参数,其中需要非null(参数2)[-Wnonnull] sc.c:22:5:警告:null参数,其中需要非null(参数2)[-Wnonnull] sc.c:24:5:警告:null参数,其中需要非null(参数1)[-Wnonnull] sc.c:24:5:警告:null参数,其中需要非null(参数1)[-Wnonnull] sc.c:25:5:警告:null参数,其中需要非null(参数1)[-Wnonnull] sc.c:25:5:警告:null参数,其中需要非null(参数2)[-Wnonnull] sc.c:25:5:警告:null参数,其中需要非null(参数1)[-Wnonnull] sc.c:25:5:警告:null参数,其中需要非null(参数2)[-Wnonnull]
clang的输出:只抱怨mystrcpywith NULL参数的直接调用。
$ clang-一切-o sc sc.c
sc.c:29:5:警告:将null传递给需要非空参数[-Wnonnull]的被调用方
mystrcpy(a,NULL);
^ ~~~~
sc.c:4:64:注意:被调用方在此处将数组参数声明为静态
静态char * mystrcpy(char dest [restrict static 1],char const src [restrict static 1]){
^ ~~~~~~~~~~~~~~~~~~~
sc.c:31:5:警告:将null传递给需要非null参数的被调用方[-Wnonnull]
mystrcpy(NULL,a);
^ ~~~~
sc.c:4:28:注意:被调用方在此处将数组参数声明为静态
静态char * mystrcpy(char dest [restrict static 1],char const src [restrict static 1]){
^ ~~~~~~~~~~~~~~~~~~~
sc.c:32:5:警告:将null传递给需要非空参数[-Wnonnull]的被调用方
mystrcpy(NULL,NULL);
^ ~~~~
sc.c:4:28:注意:被调用方在此处将数组参数声明为静态
静态char * mystrcpy(char dest [restrict static 1],char const src [restrict static 1]){
^ ~~~~~~~~~~~~~~~~~~~
sc.c:32:5:警告:将null传递给需要非空参数[-Wnonnull]的被调用方
mystrcpy(NULL,NULL);
^ ~~~~
sc.c:4:64:注意:被调用方在此处将数组参数声明为静态
静态char * mystrcpy(char dest [restrict static 1],char const src [restrict static 1]){
^ ~~~~~~~~~~~~~~~~~~~
生成4条警告。
较新版本的gcc还抱怨传递相同的指针给2个限制限定参数,但仍不支持static最小长度说明符(请参阅Godbolt session):
sc.c:30:14:警告:将参数1传递给带有参数2的'restrict'限定参数别名[-Wrestrict]
30 | mystrcpy(a,a);
| ^〜
sc.c:51:8:警告:将参数1传递给带有参数2的'restrict'限定参数别名[-Wrestrict]
51 | f3(a,a);
| ^〜
sc.c:58:8:警告:将参数1传递给带有参数2的'restrict'限定参数别名[-Wrestrict]
58 | f4(a,a);
| ^〜
sc.c:37:5:警告:“ strcpy”源参数与目标[-Wrestrict]相同
37 | f1(a,a);
| ^ ~~~~~~~~
我的问题是:这些观察结果是否符合 C 标准,或者 gcc 和/或 clang 在函数参数
static内实现 C99 关键字时是否不正确 ?[]
观察结果与标准一致。
关于诊断,该标准规定:
如果关键字
static也出现在数组类型派生的[and]中,则对于每次调用该函数,相应实参的值应提供对数组第一个元素的访问,该数组的元素数量至少与大小指定的元素一样多表达。
,但这是语义描述的一部分,而不是约束,因此不要求实现产生有关违规的诊断。当然,违反该规定的代码具有未定义的行为,但这是一个单独的问题。
即使存在违反约束的情况,符合要求的实现也没有义务拒绝该代码;在这种情况下,对实现的唯一要求是发出诊断消息。
至于函数指针类型兼容性,标准规定:
将参数声明为“类型数组”应调整为“指向类型的限定指针”,其中类型限定符(如果有)是在数组类型派生的 [ 和 ] 中指定的那些限定符。
static不在类型限定符中(它们是零个或多个const、restrict和volatile),因此它在函数签名中的出现不会改变函数的类型。因此,指向这两个函数的指针
Run Code Online (Sandbox Code Playgroud)char *mystrcpy(char dest[restrict static 1], char const src[restrict static 1]);[...]
Run Code Online (Sandbox Code Playgroud)char *strcpy(char restrict *dest, const char restrict *src);
确实有兼容的(实际上是相同的)类型。根本static 1不考虑这一点。