一尘不染

遇到的 C 的常见未定义/未指定行为是什么?

c

C 语言中未指定行为的一个示例是函数参数的求值顺序。它可能是从左到右或从右到左,你只是不知道。这将影响评估方式foo(c++, c)foo(++c, c)评估方式。

还有哪些其他未指明的行为会让不知情的程序员感到惊讶?


阅读 138

收藏
2022-03-01

共1个答案

一尘不染

语言律师问题。嗯。

我的个人top3:

  1. 违反严格的别名规则

  2. 违反严格的别名规则

  3. 违反严格的别名规则

:-)

编辑这里有一个小例子,它做错了两次:

(假设 32 位整数和小端)

float funky_float_abs (float a)
{
  unsigned int temp = *(unsigned int *)&a;
  temp &= 0x7fffffff;
  return *(float *)&temp;
}

该代码试图通过直接在浮点表示中与符号位进行位旋转来获得浮点的绝对值。

但是,通过从一种类型转换为另一种类型来创建指向对象的指针的结果是无效的 C。编译器可能会假定指向不同类型的指针不指向同一块内存。这适用于除 void 和 char 之外的所有类型的指针(符号无关紧要)。

在上述情况下,我这样做了两次。一次获取浮点 a 的 int-alias,一次将值转换回浮点数。

有三种有效的方法可以做到这一点。

在强制转换期间使用 char 或 void 指针。这些总是别名为任何东西,所以它们是安全的。

float funky_float_abs (float a)
{
  float temp_float = a;
  // valid, because it's a char pointer. These are special.
  unsigned char * temp = (unsigned char *)&temp_float;
  temp[3] &= 0x7f;
  return temp_float;
}

使用内存复制。Memcpy 采用 void 指针,因此它也会强制使用别名。

float funky_float_abs (float a)
{
  int i;
  float result;
  memcpy (&i, &a, sizeof (int));
  i &= 0x7fffffff;
  memcpy (&result, &i, sizeof (int));
  return result;
}

第三种有效方式:使用联合。自 C99 以来,这显然不是未定义的:

float funky_float_abs (float a)
{
  union 
  {
     unsigned int i;
     float f;
  } cast_helper;

  cast_helper.f = a;
  cast_helper.i &= 0x7fffffff;
  return cast_helper.f;
}
2022-03-01