c - C 中未定义的行为实际上会发生什么

标签 c gcc undefined-behavior

关闭。这个问题需要更多 focused .它目前不接受答案。












想改进这个问题?更新问题,使其仅关注一个问题 editing this post .

4年前关闭。




Improve this question




我读过很多关于未定义行为(UB)的文章,但都在谈论理论。我想知道会发生什么在实践中 , 因为包含 UB 的程序实际上可能会运行。

我的问题与类unix系统有关,而不是嵌入式系统。

我知道不应该编写依赖于未定义行为的代码。请不要发送这样的答案:

  • 一切都可能发生
  • 守护进程可以飞出你的 Nose
  • 电脑会跳起火

  • 尤其是第一个,这是不正确的。您显然无法通过执行有符号整数溢出来获得 root。我问这个只是为了教育目的。

    问题一)

    Source

    implementation-defined behavior: unspecified behavior where each implementation documents how the choice is made



    implementation编译器?

    问题 B)
    *"abc" = '\0';
    

    如果发生段错误以外的其他事情,我是否需要破坏我的系统?即使无法预测,实际会发生什么?第一个字节可以设置为零吗?还有什么,以及如何?

    问题 C)
    int i = 0;
    foo(i++, i++, i++);
    

    这是 UB,因为评估参数的顺序是未定义的。对。但是,当程序运行时,谁来决定评估参数的顺序:是编译器、操作系统还是其他?

    问题 D)

    Source
    $ cat test.c
    int main (void)
    {
        printf ("%d\n", (INT_MAX+1) < 0);
        return 0;
    }
    $ cc test.c -o test
    $ ./test
    Formatting root partition, chomp chomp
    

    根据其他 SO 用户的说法,这是可能的。这怎么可能发生?我需要一个损坏的编译器吗?

    问题 E)

    使用与上面相同的代码。除了表达式 (INT_MAX+1) 之外,实际会发生什么产生一个随机值?

    问题 F)

    GCC -fwrapv 选项定义了有符号整数溢出的行为,还是只让 GCC 假设它会回绕,但实际上它可能不会在运行时回绕?

    问题 G)

    这涉及嵌入式系统。当然,如果 PC 跳到一个意想不到的地方,两个输出可能会连在一起并造成短路(例如)。

    但是,当执行类似这样的代码时:
    *"abc" = '\0';
    

    PC不会被引导到通用异常处理程序吗?或者我错过了什么?

    最佳答案

    在实践中,大多数编译器通过以下任一方式使用未定义的行为:

  • 在编译时打印警告,通知用户他可能犯了一个错误
  • 推断变量值的属性并使用它们来简化代码
  • 执行不安全的优化,只要它们只破坏未定义行为的预期语义

  • 编译器通常不是为恶意而设计的。利用未定义行为的主要原因通常是从中获得一些性能优势。但有时这可能涉及完全消除死代码。

    一)是的。编译器应该记录他选择的行为。但通常这很难预测或解释 UB 的后果。

    B) 如果字符串实际在内存中实例化并且在可写页面中(默认情况下它将在只读页面中),那么它的第一个字符可能会变为空字符。最有可能的是,整个表达式将作为死代码被丢弃,因为它是一个从表达式中消失的临时值。

    C) 通常,评估的顺序由编译器决定。在这里它可能决定将其转换为 i += 3 (或 i = undef 如果它很傻)。 CPU 可以在运行时重新排序指令,但如果它破坏了其指令集的语义(编译器通常不能将 C 语义进一步向下转发),则保留编译器选择的顺序。一个寄存器的增量不能与同一个寄存器的另一个增量交换或并行执行。

    D) 您需要一个愚蠢的编译器,当它检测到未定义的行为时会打印“格式化根分区,chomp chomp”。最有可能的是,它会在编译时打印一个警告,用他选择的常量替换表达式并生成一个二进制文件,该二进制文件只需使用该常量执行打印。

    E) 这是一个语法正确的程序,因此编译器肯定会生成一个“工作”二进制文件。从理论上讲,该二进制文件与您可以在 Internet 上下载并运行的任何二进制文件具有相同的行为。最有可能的是,您会得到一个立即退出的二进制文件,或者打印上述消息并立即退出。

    F) 它告诉 GCC 假设有符号整数使用 2 的补码语义在 C 语义中环绕。因此,它必须生成一个在运行时环绕的二进制文件。这相当容易,因为无论如何大多数架构都有这种语义。 C 有 UB 的原因是编译器可以假设 a + 1 > a这对于证明循环终止和/或预测分支至关重要。这就是为什么使用有符号整数作为循环归纳变量可以导致更快的代码,即使它被映射到硬件中完全相同的指令。

    G) 未定义的行为是未定义的行为。生成的二进制文件确实可以运行任何指令,包括跳转到未指定的位置……或者完全触发中断。最有可能的是,您的编译器将摆脱这种不必要的操作。

    关于c - C 中未定义的行为实际上会发生什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46119976/

    相关文章:

    c - 文件描述符和系统调用

    c++ - 如何在C中使用unwrap函数和iota(复数中的 "i")?

    gcc - 使用 gcc 时跳过 scanf() (和 cin)语句

    c - c程序中的段错误

    c# - C# 中具有未定义行为的代码

    c - 为什么这些构造使用增量前和增量后未定义的行为?

    c - 陷入 fscanf 循环

    c - 我如何混合使用字符串文字和 float 并将它们连接成 C 中的一个字符串?

    gcc - 使用 GCC 编译器的 ARM 核心堆栈回溯(当有 MSP 到 PSP 切换时)

    c - 未定义行为或非未定义行为