在 C 中没有参数的 printf() 可以正常编译。如何?

printf() with no arguments in C compiles fine. how?

我尝试了下面的 c 程序,我预计会出现编译时错误,但为什么编译器没有给出任何错误?

#include <stdio.h>
int main(void)
{
    printf("%d\n");
    return 0;
}

为什么输出依赖于编译器? 这是各种编译器的输出

Orwell Dev C++ 的输出IDE(使用 gcc 4.8.1):0

Visual Studio 2010 提供的 Visual C++ 输出:0

CodeBlocks IDE(使用 gcc 4.7.1):垃圾值

在线编译器ideone.com:垃圾值

这里出了什么问题?

由于 C 可变参数的工作方式,编译器无法跟踪它们的正确用法。提供函数运行所需的更少或更多参数仍然(在语法上)合法,尽管在查看标准时这通常以未定义的行为结束。

printf 的声明如下所示:

int printf(const char*, ...);

编译器只看到 ...,并且知道该函数可能使用也可能不使用零个或多个附加参数。被调用函数不知道它传递了多少参数;它充其量可以假设它已经传递了它需要的所有信息,仅此而已。

将此与其他语言(如 C#)进行对比:

void WriteLine(string format, params object[] arguments);

这里,该方法确切地知道传递了多少个附加参数(执行arguments.Length)。

在 C 中,可变参数函数,尤其是 printf 是安全漏洞的常见原因。 Printf 最终会从堆栈中读取原始字节,这可能会泄露有关您的应用程序及其安全环境的重要细节。

出于这个原因,Clang 和 GCC 支持一个特殊的扩展来验证 printf 格式。如果您使用无效的格式字符串,您将收到警告(不是错误)。

code.c:4:11: warning: more '%' conversions than data arguments [-Wformat]
    printf("%d\n");
           ~~^

根据 this documentation,附加参数必须至少与第一个参数中的格式说明符一样多。这似乎是未定义的行为。

一般诊断消息(你可能认为它们像编译错误)不能保证 undefined behaviors(就像你的情况下 printf 函数调用缺少足够的参数),即不算作语法规则或约束违规。

C11 (N1570) §5.1.1.3/p1 Diagnostics:

A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined or implementation-defined. Diagnostic messages need not be produced in other circumstances.9)

换句话说,你的编译器可以自由翻译这样的单元,但你永远不应该 运行 它或依赖它的行为(因为它实际上是不可预测的)。此外,您的编译器被允许不提供任何文档(即 C 标准不强制它这样做),就像实现定义的或特定于语言环境的行为所必需的。

C11 (N1570) §3.4.3/p2 undefined behavior:

NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

这编译得很好。因为它匹配

的 printf() 原型
printf(const char *,...);

在 运行 通话期间

printf("%d\n");

尝试从第二个参数获取值,由于您没有传递任何东西,它可能会得到一些垃圾值并将其打印出来,因此这里的行为是未定义的。

您的程序可以正常编译,因为 printf() 是一个可变参数函数,默认情况下不执行格式说明符数量与所提供参数的匹配检查。

在运行时,您的程序显示 undefined behaviour,因为没有提供必须使用提供的格式说明符打印的参数。

根据第 7.19.6.1 章,c99 标准,(来自 fprintf()

If there are insufficient arguments for the format, the behavior is undefined.

如果您在 gcc 中使用 -Wformat 标志进行编译,您的编译器将产生不匹配的警告。

您正在调用未定义的行为。那是你的问题,而不是编译器的问题,基本上任何事情都是 "allowed" 发生的。

当然,实际上每个现有的编译器都应该能够警告您有关 printf() 的这种特殊情况,您只需要允许他(通过启用并注意编译器警告)。

这只是 undefined behavior if you do not provide the sufficient arguments to printf, which mean the behavior is unpredictable. From the draft C99 standard 部分 7.19.6.1 fprintf 函数 也涵盖了 printf 这种情况:

If there are insufficient arguments for the format, the behavior is undefined.

因为 printfvariadic function there is no matching of arguments to the function declaration. So the compiler needs to support support format string checking which is covered by -Wformat flag in gcc:

Check calls to printf and scanf, etc., to make sure that the arguments supplied have types appropriate to the format string specified, and that the conversions specified in the format string make sense. This includes standard functions, and others specified by format attributes (see Function Attributes), [...]

启用足够的编译器警告很重要,因为这段代码 gcc 使用 -Wall 标志告诉我们 (see it live):

 warning: format '%d' expects a matching 'int' argument [-Wformat=]
 printf("%d\n");
 ^

您使用编译器得到了什么warnings/messages? 我 运行 这是通过 gcc (Ubuntu 4.8.2-19ubuntu1) 得到的警告

warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=]
     printf("%d\n");
     ^

和 运行 它也 "garbage output"。 在这里,gcc 非常聪明地解析格式表达式并通知编码器提供匹配数量的参数。

我认为会发生什么: printf 的函数签名与编译代码的行为独立。在编译时,编译器只关心检查是否至少有一个参数存在并继续。然而,编译后的函数将首先解析格式表达式,并根据它从函数的参数堆栈中读取更多参数。其中它只是期望适当地放置值(int、float 等)并使用它们。 因此,如果您不指定参数,则函数调用堆栈上的任何位置都不会保留,并且 printf 仍会读取 运行dom 内存(在本例中为第一个位置)。这也解释了 "garbage" 输出,每次调用二进制文件时都会有所不同。您甚至可以将代码扩展到

#include <stdio.h>
int main(void)
{
    printf("%d\n%d\n");
    return 0;
}

并得到两个不同的垃圾号码:)

然后它将再次取决于 environment/process/compiler 将读取哪些值。 "unspecified behaviour" 最能描述这种效果,有时为零,有时为其他。

我希望这能澄清您的问题!

g++ 与命令行参数 -Wall 一起使用会产生以下诊断信息:

g++ -Wall   -c -g -MMD -MP -MF "build/Debug/MinGW-Windows/main.o.d" -o build/Debug/MinGW-Windows/main.o main.cpp
main.cpp: In function 'int main(void)':
main.cpp:17:16: warning: format '%d' expects a matching 'int' argument [-Wformat=]
     printf("%d");
                ^

这很有用,不是吗?

gcc/g++ 还检查格式说明符是否与参数类型实际匹配。这对于调试来说真的很酷。

在 printf() 和 fprintf() 的上下文中,根据 C 标准 C11 条款 7.21.6.1,"If there are insufficient arguments for the format, the behavior is undefined. If the format is exhausted while arguments remain, the excess arguments are evaluated (as always) but are otherwise ignored."