在C中没有参数的printf()编译好。怎么样?

时间:2015-01-28 14:04:39

标签: c printf undefined-behavior

我尝试了以下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:垃圾值

这里出了什么问题?

10 个答案:

答案 0 :(得分:8)

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

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

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

  

如果格式的参数不足,则行为为   未定义。

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

答案 1 :(得分:5)

由于C variadic参数的工作原理,编译器无法跟踪它们的正确用法。提供函数需要更少或更多的参数仍然是(语法上)合法的,尽管这通常在查看标准时最终会被定义为未定义的行为。

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");
           ~~^

答案 2 :(得分:4)

如果你没有为printf提供足够的参数,这只是undefined behavior,这意味着行为是不可预测的。来自draft C99 standard部分7.19.6.1 fprintf函数,在此案例中也涵盖了printf

  

如果格式的参数不足,则行为为   未定义。

由于printfvariadic function,因此函数声明的参数没有匹配。因此编译器需要支持-Wformat flag in gcc所涵盖的支持格式字符串检查:

  

检查对printf和scanf等的调用,以确保提供的参数具有适合指定格式字符串的类型,并且格式字符串中指定的转换是有意义的。这包括标准函数,以及格式属性指定的其他函数(参见函数属性),[...]

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

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

答案 3 :(得分:3)

编译好。因为它匹配

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

在运行期间调用

printf("%d\n");

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

答案 4 :(得分:2)

一般来说,undefined behaviors不能保证诊断消息(你可能会认为它们像编译错误一样)(比如你的情况下printf函数调用缺少足够的参数),这些消息不计算在内作为语法规则或约束违规。

  

C11(N1570)§5.1.1.3/ p1 诊断

     

符合要求的实施应至少产生一种诊断   消息(以实现定义的方式标识)如果a   预处理翻译单元或翻译单元包含一个   违反任何语法规则或约束,即使行为是   也明确指定为未定义或实现定义。   在其他情况下,无需生成诊断消息 9)

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

  

C11(N1570)§3.4.3/ p2 未定义的行为

     

注意可能的未定义行为包括忽略这种情况   完全具有不可预测的结果,在翻译过程中表现出色   或者以文件化的方式执行程序   环境(有或没有发出诊断信息),到   终止翻译或执行(发布a   诊断信息)。

答案 5 :(得分:2)

您正在调用未定义的行为。这是你的问题,而不是编译器,基本上任何东西都是允许的#34;发生。

当然,几乎每个现有的编译器都应该能够警告你关于printf()的这个特殊情况,你只需要允许他(通过启用和注意编译器警告)。

答案 6 :(得分:2)

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++还检查格式说明符是否与参数类型实际匹配。这对于调试来说非常酷。

答案 7 :(得分:1)

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

答案 8 :(得分:1)

您使用编译器获得了哪些警告/消息? 我通过gcc(Ubuntu 4.8.2-19ubuntu1)运行它并收到警告

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

并运行它&#34;垃圾输出&#34;。 在这里,gcc非常聪明地解析格式表达式并通知编码器提供匹配数量的参数。

我认为发生了什么: printf的函数签名与编译代码的行为独立。在编译时,所有编译器关心的是检查是否存在至少一个参数并继续。但是,编译后的函数将首先解析格式表达式,并根据该表达式从函数中读取更多参数。参数堆栈。其中只需要适当放置值(int,float等)并使用它们。 因此,如果你没有指定参数,那么函数调用堆栈上没有任何地方被保留,printf仍然会读取随机存储器(在这种情况下是在第一个位置)。这也解释了&#34;垃圾&#34;输出,每次调用二进制文件时都会有所不同。你甚至可以将代码扩展到

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

并获取两个不同的垃圾编号:)

然后再次取决于环境/进程/编译器将读取哪些值。 &#34;未指明的行为&#34;是最好地描述这种效果,有时是零,有时是其他。

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

答案 9 :(得分:-1)

在printf()和fprintf()的上下文中,根据C标准C11子句7.21.6.1,&#34;如果格式的参数不足,则行为未定义。如果参数保留时格式已用尽,则会评估多余的参数(一如既往),否则将被忽略。&#34;