代码之家  ›  专栏  ›  技术社区  ›  Benoit

您遇到的C的常见未定义/未指定行为是什么?[关闭]

  •  62
  • Benoit  · 技术社区  · 16 年前

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

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

    13 回复  |  直到 16 年前
        1
  •  70
  •   user3447428    11 年前

    语言律师问题。Hmkay。

    我的个人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*之外的所有类型的指针都是正确的(符号不重要)。

    在上面的例子中,我做了两次。一次获取float A的int别名,一次将值转换回float。

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

    在转换期间使用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;
    }
    

    使用MeMe副本。memcpy使用空指针,因此它也将强制进行混叠。

    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;
    }
    
        2
  •  30
  •   Steve Jessop    11 年前

    我个人最喜欢的未定义行为是,如果一个非空源文件没有以换行符结尾,那么行为是未定义的。

    我怀疑这是真的,尽管没有一个编译器会根据源文件是否被换行终止而区别对待它,除了发出警告。因此,这并不是真正让不知情的程序员感到惊讶的事情,而是他们可能会对警告感到惊讶。

    因此,对于真正的可移植性问题(这些问题主要取决于实现,而不是未指定或未定义,但我认为这符合问题的精神):

    • char不一定是(un)有符号的。
    • int可以是16位中的任意大小。
    • 浮点数不一定是IEEE格式或一致的。
    • 整数类型不一定是二的补码,整数算术溢出会导致未定义的行为(现代硬件不会崩溃,但某些编译器优化会导致不同于wraporound的行为,即使这是硬件所做的。例如 if (x+1 < x) 可能被优化为总是错误的,当 x 签名类型:请参见 -fstrict-overflow GCC中的期权。
    • 在a include中的“/”、“.”和“..”没有定义的含义,可以由不同的编译器进行不同的处理(这实际上是不同的,如果它出错,将破坏您的一天)。

    非常严重的问题,即使在您开发的平台上也会令人惊讶,因为行为只是部分未定义/未指定的:

    • POSIX线程和ANSI内存模型。并发访问内存的定义不如新手所想的那样好。易失性并不像新手想的那样。记忆访问的顺序并没有新手想象的那么清楚。通道 可以 在某些方向上穿过记忆屏障。不需要内存缓存一致性。

    • 分析代码并不像您想象的那么简单。如果测试循环没有任何效果,编译器可以删除其中的一部分或全部。内联没有定义的效果。

    我认为尼尔斯顺便提过:

    • 违反了严格的别名规则。
        3
  •  22
  •   HostileFork says dont trust SE    12 年前

    用指向某物的指针来划分某物。只是因为某种原因不能编译…-)

    result = x/*y;
    
        4
  •  19
  •   1800 INFORMATION    16 年前

    我最喜欢的是:

    // what does this do?
    x = x++;
    

    回答一些意见,根据标准,这是未定义的行为。看到这一点,编译器可以对硬盘进行任何格式化(包括格式化)。 参见例如 this comment here .关键不是你能看到对某些行为可能有一个合理的预期。由于C++标准和序列点被定义的方式,这段代码实际上是未定义的行为。

    例如,如果我们 x = 1 在上面的行之前,那么之后的有效结果是什么?有人说应该是

    x增加1

    所以我们应该在后面看到x==2。然而,这并不是真的,您会发现一些编译器在后面的x==1,甚至x==3。您将不得不仔细查看生成的程序集,以了解这可能是为什么,但这些差异是由于底层问题造成的。本质上,我认为这是因为编译器可以按照它喜欢的任何顺序计算这两个赋值语句,所以它可以 x++ 第一,或者 x = 第一。

        5
  •  10
  •   itj    16 年前

    我遇到的另一个问题(已定义,但绝对出乎意料)。

    炭是邪恶的。

    • 有符号或无符号取决于编译器的感觉
    • 强制为8位
        6
  •  8
  •   Jens    11 年前

    我无法计算更正printf格式说明符以匹配其参数的次数。 任何不匹配都是未定义的行为 .

    • 不,你不能通过 int (或) long %x -安 unsigned int 是必需的
    • 不,你不能通过 无符号整型 %d -安 int 是必需的
    • 不,你不能通过 size_t %u %D -使用 %zu
    • 不,不能用 %D %x -使用 %p 铸造成 void *
        7
  •  7
  •   mbac32768    16 年前

    如果函数原型不可用,编译器不必告诉您调用的函数参数个数错误/参数类型错误。

        8
  •  6
  •   Keith Thompson    11 年前

    我见过很多相对缺乏经验的程序员被多字符常量咬伤。

    这是:

    "x"
    

    是字符串文本(类型为 char[2] 衰弱到 char* 在大多数情况下)。

    这是:

    'x'
    

    是普通字符常量(出于历史原因,它属于类型 int )

    这是:

    'xy'
    

    也是一个完全合法的字符常量,但它的值(仍然是类型 int )是否定义了实现。这是一个几乎无用的语言特性,主要是导致混淆。

        9
  •  4
  •   Per Johansson    13 年前

    Clang开发者发布了一些 great examples 不久前,在一篇文章中,每个C程序员都应该阅读。一些有趣的事情之前没有提到:

    • 有符号整数溢出-不,有符号变量超过其最大值是不可以的。
    • 取消对空指针的引用-是的,这是未定义的,可能会被忽略,请参阅链接的第2部分。
        10
  •  2
  •   Tim Williscroft    16 年前

    EE在这里刚刚发现A>>-2有点担心。

    我点点头告诉他们这不自然。

        11
  •  1
  •   William Keller    16 年前

    一定要在使用变量之前初始化它们!当我刚开始使用C时,这引起了我很多头痛。

        12
  •  0
  •   Mike Thompson    16 年前

    使用“max”或“isupper”等函数的宏版本。宏对它们的参数进行了两次评估,因此当调用max(++i,j)或isupper(*p++)时,会得到意外的副作用。

    以上是标准C。在C++中,这些问题基本上消失了。max函数现在是一个模板化函数。

        13
  •  -1
  •   Nicholas Mancuso    16 年前

    忘记添加 static float foo(); 在头文件中,只有当浮点异常返回0.0f时才会被抛出;