为什么未定义的行为如此一致?

tar*_*pka 1 c printf pointers undefined-behavior

我一直在玩指针,不小心输入了printf的错误参数

#include <stdio.h>

int
main (void)
{
  double * p1;
  double * p2; 
  double d1, d2;

  d1 = 1.2345;
  d2 = 2.3456;
  p1 = &d1;
  p2 = &d2;

  printf ("p1=%p\n, *p1=%g\n", (void *)p1, *p1);
  printf ("p2=%p\n, *p2=%g\n", (void *)p2,  p2); /* third argument should be *p2 */

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出是

警告:格式'%g'需要类型为'double'的参数,但参数3的类型为'double*'
p1 = 0x7ffc9aec46b8,*p1 = 1.2345
p2 = 0x7ffc9aec46c0,*p2 = 1.2345

为什么在这种情况下输出p2总是等于输出*p1

我使用gcc(v5.4.0)编译器及其C(gnu11)的默认标准.

tre*_*tcl 6

调用未定义行为的代码可以执行任何操作 - 这就是为什么它未定义.

也就是说,人们可以很好地猜测为什么它会在您的特定机器上使用您的特定编译器执行此特定操作时使用使用的选项并在一年同一工作日使用6进行编译,您明白了这一点,对?这是未定义的,即使您认为您知道所有变量,也没有任何解释可以依赖.有一天,湿度下降,或者某种程度,你的程序可能决定做一些不同的事情.即使没有重新编译.甚至在同一循环的两次迭代中.这就是未定义的行为.

无论如何,在您的平台上,浮点参数可能在专用浮点寄存器(或专用浮点堆栈)中传递,而不是在主堆栈上传递.printf("%g")需要一个浮点参数,因此它在浮点寄存器中查找.但是你没有在浮点寄存器中传递任何东西; 所有你传递的都是两个指针参数,它们都在堆栈上(或指针参数去的地方;这也超出了C标准的范围).因此,第二次printf调用会在上次加载时获取该特定浮点寄存器中的垃圾.只是碰巧你加载到该寄存器中的最后一件事是*p1在最后一次printf调用中的值,以便重用该值.

确定(以及其他内容)函数参数放置的规则,以便函数知道在哪里查找它们统称为调用约定.您可能正在使用x86或衍生版本,因此您可能会发现有关x86调用约定Wikipedia页面很有趣.但是如果你想知道你的编译器在做什么,请让它发出汇编语言(gcc -S).


R..*_*R.. 5

没有被定义——这就是重点。您所看到的可能是用于传递浮点参数的任何寄存器中保留的旧值的结果。