这篇文章的目的是让每个程序员(特别是 C 程序员)说:我真的不懂 C。我想要让大家看到 C 语言的那些阴暗角落比我们想象中更近,甚至那些平常的代码中包含着未定义的行为。
  这篇文章设置了一系列的问题和答案。所有的例子都是从源代码中单独分离出来的。
  1.
  int i;
  int i = 10;
  Q:这段代码正确吗?是否会因为变量被定义了两次而导致错误的出现?注意这是源于同一个源码文件,而不是函数体或代码段的一部分。
  A:是的,这段代码是正确的。第一行是临时的定义直到编译器处理了第二行的定义之后才成为正式的“定义”。
  2.
  extern void bar(void);
  void foo(int *x)
  {
  int y = *x;
  /* (1) */
  if(!x)
  /* (2) */
  {
  return;
  /* (3) */
  }
  bar();
  return;
  }
  Q: 这样写的结果是即使 x 是空指针 bar() 函数都会被调用,并且程序不会崩溃。这是否是优化器的错误,或者全部是正确的?
  A: 全部都是正确的。如果 x 是空指针,未定义的行为出现在第 (1) 行, 没有人欠程序员什么,所以程序并不会在第 (1) 行崩溃, 也不会试图在第 (2) 行返回假如已经成功运行第 (1) 行。让我们来探讨编译器遵循的规则,它都按如下的方式进行。在对第 (1) 行的分析之后,编译器认为 x 不会是一个空指针,于是第 (2) 行和 第 (3) 行被认定为是没用的代码。变量 y 被当做没用的变量去除。从内存中读取的操作也会被去除,因为 *x 并不符合易变类型(volatile)。
  这是无用的变量如何导致空指针检查失效的例子。
  3.有这样一个函数:
  #define ZP_COUNT 10
  void func_original(int *xp, int *yp, int *zp)
  {
  int i;
  for(i = 0; i < ZP_COUNT; i++)
  {
  *zp++ = *xp + *yp;
  }
  }
  有人想要按如下方式来优化它:
  void func_optimized(int *xp, int *yp, int *zp)
  {
  int tmp = *xp + *yp;
  int i;
  for(i = 0; i < ZP_COUNT; i++)
  {
  *zp++ = tmp;
  }
  }
  Q:调用原始的函数和调用优化后的函数,对于变量 zp 是否有可能获得不同的结果?
  A:这是可能的,当 yp == zp 时结果不同。
  4.
  double f(double x)
  {
  assert(x != 0.);
  return 1. / x;
  }
  Q: 这个函数是否可能返回大下界(inf) ?假设浮点数运算是按照IEEE 754 标准(大部分机器遵循)执行的, 并且断言语句是可用的(NDEBUG 并没有被定义)。
  A:是的,这是可以的。通过传入一个非规范化的 x 的值,比如 1e-309.