gcc和clang中不一致的C99支持

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。

我编写了一个测试程序来验证这两个原型的兼容性,并且惊讶地发现它们确实兼容,尽管第一个中包含的信息比第二个中包含的信息更多。这些事实进一步令人惊讶:

  • 在启用所有警告的情况下,clang不会抱怨strcpy接收空参数。
  • 尽管gcc 定义明确,但确实抱怨strcpy接收了空参数,而不是不接收mystrcpy
  • 分配strcpymystrcpy给使用两种语法定义的函数指针都不会引起任何警告。
  • 通过函数指针将空指针传递给间接调用不会在直接调用发生的clang中触发警告。

我的问题是:这些观察结果是否与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);
      | ^ ~~~~~~~~

Joh*_*ger 1

我的问题是:这些观察结果是否符合 C 标准,或者 gcc 和/或 clang 在函数参数static内实现 C99 关键字时是否不正确 ?[]

观察结果与标准一致。

关于诊断,该标准规定:

如果关键字static也出现在数组类型派生的[and]中,则对于每次调用该函数,相应实参的值应提供对数组第一个元素的访问,该数组的元素数量至少与大小指定的元素一样多表达。

,但这是语义描述的一部分,而不是约束,因此不要求实现产生有关违规的诊断。当然,违反该规定的代码具有未定义的行为,但这是一个单独的问题。

即使存在违反约束的情况,符合要求的实现也没有义务拒绝该代码;在这种情况下,对实现的唯一要求是发出诊断消息。

至于函数指针类型兼容性,标准规定:

将参数声明为“类型数组”应调整为“指向类型的限定指针”,其中类型限定符(如果有)是在数组类型派生的 [ 和 ] 中指定的那些限定符。

static不在类型限定符中(它们是零个或多个constrestrictvolatile),因此它在函数签名中的出现不会改变函数的类型。因此,指向这两个函数的指针

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);
Run Code Online (Sandbox Code Playgroud)

确实有兼容的(实际上是相同的)类型。根本static 1不考虑这一点。