首页 文章

为什么C标准允许编译没有错误? [关闭]

提问于
浏览
5

这是一段似乎被接受而没有错误的代码:

#include <stdio.h>
#include <string.h>

int main() {
    if (strcmp(1, 2))
        printf(3);
}

使用 clang -std=c11 -Weverything 进行编译会产生4个警告:

badstrcmp.c:5:16: warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'const char *' [-Wint-conversion]
    if (strcmp(1, 2))
               ^
/usr/include/string.h:77:25: note: passing argument to parameter '__s1' here
int      strcmp(const char *__s1, const char *__s2);
                            ^
badstrcmp.c:5:19: warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'const char *' [-Wint-conversion]
    if (strcmp(1, 2))
                  ^
/usr/include/string.h:77:43: note: passing argument to parameter '__s2' here
int      strcmp(const char *__s1, const char *__s2);
                                              ^
badstrcmp.c:6:16: warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'const char *' [-Wint-conversion]
        printf(3);
               ^
/usr/include/stdio.h:259:36: note: passing argument to parameter here
int      printf(const char * __restrict, ...) __printflike(1, 2);
                                       ^
badstrcmp.c:6:16: warning: format string is not a string literal (potentially insecure) [-Wformat-security]
        printf(3);
               ^
badstrcmp.c:6:16: note: treat the string as an argument to avoid this
        printf(3);
               ^
               "%s",
4 warnings generated.

我的问题是 Why does the C Standard allow it to compile? . 这类问题应该被诊断为错误,应该拒绝代码 . 为什么C标准允许翻译程序?

5 回答

  • 5
    #include <stdio.h>
    #include <string.h>
    
    int main() {
        if (strcmp(1, 2))
            printf(3);
    }
    

    strcmpprintf 的调用,因为它们传递了不正确类型的参数(并且没有可用的隐式转换),所以是约束违规 .

    对于违反约束或语法规则的任何程序,C标准至少需要一条诊断消息 . 就标准而言,警告是完全有效的诊断消息 .

    这是标准所说的内容(引用C11标准的N1570草案,第5.1.1.3节):

    如果预处理转换单元或转换单元包含违反任何语法规则或约束,则符合实现应生成至少一条诊断消息(以实现定义的方式标识),即使该行为也明确指定为未定义或实现-defined . 在其他情况下不需要产生诊断消息 .

    标准实际上要求拒绝源文件的唯一情况是它包含 #error 指令 . 第4节第4段:

    实现不应成功转换包含#error预处理指令的预处理转换单元,除非它是由条件包含跳过的组的一部分 .

    我个人更喜欢gcc和clang拒绝违反语法规则或约束的程序,但正如我所说,标准允许非致命警告 . 如果您希望拒绝此类程序,可以使用各种选项,例如 -std=c11 -pedantic-errors . 请注意,默认情况下gcc和clang并不完全符合C标准,但这些非致命警告不是该不一致的示例 .

  • 3

    您的问题似乎是基于C Standard以某种方式“允许此代码编译”的无效前提 . 实际上,C Standard没有“允许或不允许代码编译”这样的概念 .

    如果代码无效,则标准要求编译器通过诊断消息告诉您 . 标准不要求编译器拒绝编译您的代码 . 他们仍然可以继续以一些实现定义的方式编译它 .

    根据C标准,您的代码明显无效 . 标准C语言不允许隐式整数到指针转换 . 您的编译器通过诊断消息清楚地告诉您这一点 . 这足以使编译器满足标准的要求 .

    之后所有的赌注都没有了 . 您的编译器可能会将其编译为“某些东西”,但这不是一个符合C的程序 . 它的行为不是由语言定义的 .

    至于您收到的诊断消息的格式(以及它们是"warnings"还是"errors") - 这是您的编译器的问题 . 在C中,这是一个实施质量问题 . C标准与它无关 .

    您可以通过提供 -pedantic-errors 标志,要求 clang 将C语言约束违规报告为"errors" . 它不是完美的目的,但它会让编译器拒绝编译你的代码(如果这是你想要的) .

  • 6

    并且应该拒绝代码 .

    那是一个很大的假设 .

    要实际引用标准:

    只要仍然正确翻译了有效的程序,实现就可以自由地生成任意数量的诊断 . 它也可能成功翻译无效程序 .

    5.1.1.3 note 9

    当遇到错误时,标准对编译器必须做的事情没有很大的限制,正是由于在许多情况下和许多平台上,“错误”实际上可以很好地定义(通过实现)扩展行为 . 从向开发人员提供有用信息的角度来看,在程序的以下部分中继续翻译和获取其他错误或信息也可能更好,而不是放弃第一个非常小的,仍然在句法上有效的难点 .

    它还倾向于不对"translation"究竟涉及的内容做出太多假设(翻译阶段7基本上只是"something happens here") . 没有"machine code"的概念(不包括像J.5.7这样的扩展名)例如,在严格标准版本的语言中,标准不能禁止编译器发出它 .

  • 4

    一方面,标准明确允许整数到指针类型转换(参见C99 standard):

    6.3.2.3指针...(5)整数可以转换为任何指针类型 . 除非先前指定,否则结果是实现定义的,可能未正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示.56)

    另一方面,标准要求在这种情况下进行明确的演员:

    6.5.4演员操作员......(3)除了6.5.16.1的约束条件允许的情况外,涉及指针的转换应通过显式转换来指定

    所以 printf(3) 显然不正确,但它是否会产生错误或警告似乎是 - 在这种情况下 - 受制于编译器 .

  • 3

    如果您不想编译此类代码,请使用 -Werror 作为附加标志,将每个警告转换为编译错误,并且不会编译您的代码 .

相关问题