C语言 传递错误类型参数时函数的奇怪行为



我有以下程序

void swap(float * x, float * y)
{
  float aux
  aux = *x;
  *x = *y;
  *y = aux;
}
int main(void)
{
  double a = 3.5, b = 5.6;
  swap(&a, &b);
  printf("%g %gn", a, b);
  return 0;
}

程序编译,它显然抛出一些警告,但它运行,并且a, b的值不会被交换。我不明白发生了什么,我会认为这要么不会编译,要么它会起作用,但这是非常不同的。

发生了什么事情?

程序调用未定义的行为,因此任何行为都是可能的。编译器不需要检测和拒绝这种情况,尽管优秀的编译器通常会警告这样的可疑代码。

它不会崩溃的可能原因是为 swap 函数生成的代码并不真正关心变量中的值,因为它没有对它们执行任何算术。由于它只是从一个变量复制到另一个变量,它只是将内存复制为字节 - 它与以下变量没有太大区别:

memcpy(&aux, x, sizeof(float));
memcpy(x, y, sizeof(float));
memcpy(y, &auz, sizeof(float));

这样做是交换 32 如果两个双打的 64 位。您没有看到任何变化的原因是这些是值的低阶位,它们被四舍五入。

此演示显示了交换前后两个十六进制值的内部表示形式(使用更多的类型双关语和 printf )。

http://ideone.com/boN3Df

扩展Barmar的答案:你遇到的未定义行为是由于严格的混叠。严格锯齿是要求指向类型 A 的指针不能强制转换为类型 B 的指针,除非 B 是字节大小(或结构字段的一些魔术,请参阅什么是严格锯齿规则?)。我很想知道如果您使用 -fno-strict-aliasing 编译,它是否会交换元素.

编辑:除此之外,当您传递违反严格别名规则的函数指针时,编译器可以对该函数调用执行任何它想做的事情。在这种情况下,它可能只是忽略了它,因为在这种情况下它可以做的最快的事情是什么都没有。您可以检查的另一件事是,在启用和未启用优化的情况下,行为是否不同。

如果您尝试使用带有较长小数部分的双精度,您可以看到差异。

void swap(float * x, float * y)
{
  float aux = *x;
  *x = *y;
  *y = aux;
}
int main(void)
{
  double a = 3.0, b = 5.987654321;
  printf("%f %fn", a, b);
  swap(&a, &b);
  printf("%f %fn", a, b);
  return 0;
}

输出为:

3.000000 5.987654
3.000001 5.987652

标准双精度为 8 个字节,浮点数为 4 个字节。使用编译器,交换有效地交换每个浮点数的 4 位(它仍然是一个未定义的行为)。

最新更新