我尝试了以下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:垃圾值
这里出了什么问题?
答案 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
:
如果格式的参数不足,则行为为 未定义。
由于printf
是variadic 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;