打印%d时printf的行为而不提供变量名称

我刚刚遇到一个奇怪的问题,我正在尝试printf一个整数变量,但是我忘了指定变量名,即

printf("%d"); 

代替

 printf("%d", integerName); 

令人惊讶的是程序编译,有输出,它不是随机的。 事实上,它恰好是我想要首先打印的整数,恰好是m-1。

只要程序一直运行,错误的printf语句将始终输出m-1 …换句话说,它的行为就像语句读取一样

 printf("%d", m-1); 

谁知道这种行为背后的原因? 我在没有任何命令行选项的情况下使用g ++。

 #include  #define maxN 100 #define ON 1 #define OFF 0 using namespace std; void clearArray(int* array, int n); int fillArray(int* array, int m, int n); int main() { int n = -1, i, m; int array[maxN]; int found; scanf("%d", &n); while(n!=0) { found=0; m = 1; while(found!=1) { if(m != 2 && m != 3 && m != 4 && m != 6 && m != 12) { clearArray(array, n); if(fillArray(array, m, n) == 0) { found = 1; } } m++; } printf("%d\n"); scanf("%d", &n); } return 0; } void clearArray(int* array, int n) { for(int i = 1; i  n) i = 1; if(*(array+i) == ON) incrementCounter++; } } return 0; } 

你说“令人惊讶的是程序编译”。 实际上,这并不奇怪。 C&C ++允许函数具有可变参数列表。 printf的定义是这样的:

 int printf(char*, ...); 

“…”表示该函数有零个或多个可选参数。 实际上,C具有可选参数的主要原因之一是支持printf&scanf系列函数。

C没有printf函数的特殊知识。 在你的例子中:

 printf("%d"); 

编译器不分析格式字符串并确定缺少整数参数。 这是完全合法的C代码。 您缺少参数的事实是仅在运行时出现的语义问题。 printf函数将假定您已提供参数并在堆栈中查找它。 它会接收到那里发生的任何事情。 只是在您的特殊情况下它正在打印正确的东西,但这是一个例外。 通常,您将获得垃圾数据。 这种行为因编译器而异,并且也会根据您使用的编译选项而改变; 如果你打开编译器优化,你可能会得到不同的结果。

正如我的回答中的一条评论所指出的,一些编译器具有“lint”function,可以实际检测错误的printf / scanf调用。 这涉及编译器解析格式字符串并确定预期的额外参数的数量。 这是非常特殊的编译器行为,并且在一般情况下不会检测错误。 即如果您编写自己的“printf_better”函数,该函数与printf具有相同的签名,则编译器将不会检测是否缺少任何参数。

看起来像这样。

 printf("%d", m); 

在大多数系统中,字符串的地址将被推入堆栈,然后'm'作为整数(假设它是int / short / char)。 没有警告,因为printf基本上被声明为'int printf(const char *, ...);' – ……意思是“一切顺利”。

因此,“任何事情都会发生”,当你把变量放在那里时会发生一些奇怪 任何小于int的整数类型都是int – 就像那样。 什么都不发送也没问题。

在printf实现中(或者至少是一个’简单’实现),你会发现va_listva_arg (某些时候名称根据一致性略有不同)。 这些是实现用于遍历参数列表的“…”部分的内容。 这里的问题是没有类型检查。 由于没有类型检查, printf会在查看格式字符串("%d")时从执行堆栈中提取随机数据,并认为接下来应该是'int'

在黑暗中随机拍摄会说你在printf之前调用的函数可能会通过'm-1'作为它的第二个parm吗? 这是许多可能性中的一种 – 但如果碰巧是这种情况会很有趣。 🙂

祝好运。

顺便说一句 – 大多数现代编译器(我相信GCC?)都有警告可以启用来检测这个问题。 我相信皮棉也是如此。 不幸的是我认为使用VC你需要使用/ analyze标志而不是免费获得。

它从堆栈中获得了一个int。

http://en.wikipedia.org/wiki/X86_calling_conventions

你凝视着堆栈。 更改优化程序值,这可能会更改。 更改变量(特别是) m的声明顺序。 使m成为寄存器变量。 使m成为全局变量。

你会看到发生了什么变化。

这与您执行简单I / O时获得的着名缓冲区溢出黑客类似。

虽然我非常怀疑这会导致内存违规,但你得到的整数是未定义的垃圾。

你找到了一个行为。 它可能是任何其他行为,包括无效的内存访问。