【问题标题】:Output of C program changes when optimisation is enabled启用优化时 C 程序的输出发生变化
【发布时间】:2021-12-15 02:47:21
【问题描述】:

我正在解决 CS:APP 课程中的一个实验练习作为自学。

在 CS:APP 课程中,最大正数可以用二进制补码中的 4 个字节表示,标记为Tmax(等于0x7fffffff)。

同样,最大负数标记为Tmin(等于0x80000000)。

练习的目标是实现一个isTmax() 函数,当给定Tmax 时,它应该返回1,否则它应该返回0。这只能使用一组受限的运算符来完成,它们是:@987654327 @,最大运算符数为 10。

您可以在下面看到我对isTmax() 函数的实现,cmets 解释了它应该如何工作。

#include <stdio.h>

int isTmax(int x) 
{
    /* Ok, lets assume that x really is tMax.
     * This means that if we add 1 to it we get tMin, lets call it
     * possible_tmin. We can produce an actual tMin with left shift.
     * We can now xor both tmins, lets call the result check.
     * If inputs to xor are identical then the check will be equal to
     * 0x00000000, if they are not identical then the result will be some
     * value different from 0x00000000.
     * As a final step we logicaly negate check to get the requested behaviour.
     * */
    int possible_tmin = x + 1;
    int tmin = 1 << 31;
    int check = possible_tmin ^ tmin;
    int negated_check = !check;

    printf("input =\t\t 0x%08x\n", x);
    printf("possible_tmin =\t 0x%08x\n", possible_tmin);
    printf("tmin =\t\t 0x%08x\n", tmin);
    printf("check =\t\t 0x%08x\n", check);
    printf("negated_check =\t 0x%08x\n", negated_check);

    return negated_check;
}

int main() 
{
    printf("output: %i", isTmax(0x7fffffff));

    return 0;
}

我面临的问题是在编译程序时是否设置了优化标志会得到不同的输出。我正在使用gcc 11.1.0

没有优化我得到这个输出,这对于给定的输入是正确的:

$ gcc main.c -lm -m32 -Wall && ./a.out
input =          0x7fffffff
possible_tmin =  0x80000000
tmin =           0x80000000
check =          0x00000000
negated_check =  0x00000001
output: 1

启用优化后,我得到了这个输出,这是不正确的。

gcc main.c -lm -m32 -Wall -O1 && ./a.out
input =          0x7fffffff
possible_tmin =  0x80000000
tmin =           0x80000000
check =          0x00000000
negated_check =  0x00000000
output: 0

由于某种原因,启用优化时逻辑否定未应用于check 变量。

在任何其他优化级别(-O2-O3-Os)下问题仍然存在。 即使我将表达式写成单行 return !((x + 1) ^ (1 &lt;&lt; 31)); 也没有任何变化。

如果我将 check 声明为 volatile,我可以“强制”正确的行为。

我正在使用与练习附带的自动检查器相同的优化级别,如果我将其关闭,我的代码将通过所有检查。

谁能解释为什么会发生这种情况?为什么逻辑否定不会发生?

编辑:我添加了一个部分,其中包含与我忘记包含在原始帖子中的练习相关的额外指南和限制。具体来说,我不允许使用任何其他数据类型来代替int。我不确定这是否还包括文字后缀U

  Replace the "return" statement in each function with one
  or more lines of C code that implements the function. Your code
  must conform to the following style:

  int Funct(arg1, arg2, ...) {
      /* brief description of how your implementation works */
      int var1 = Expr1;
      ...
      int varM = ExprM;

      varJ = ExprJ;
      ...
      varN = ExprN;
      return ExprR;
  }

  Each "Expr" is an expression using ONLY the following:
  1. Integer constants 0 through 255 (0xFF), inclusive. You are
      not allowed to use big constants such as 0xffffffff.
  2. Function arguments and local variables (no global variables).
  3. Unary integer operations ! ~
  4. Binary integer operations & ^ | + << >>

  Some of the problems restrict the set of allowed operators even further.
  Each "Expr" may consist of multiple operators. You are not restricted to
  one operator per line.

  You are expressly forbidden to:
  1. Use any control constructs such as if, do, while, for, switch, etc.
  2. Define or use any macros.
  3. Define any additional functions in this file.
  4. Call any functions.
  5. Use any other operations, such as &&, ||, -, or ?:
  6. Use any form of casting.
  7. Use any data type other than int.  This implies that you
     cannot use arrays, structs, or unions.


  You may assume that your machine:
  1. Uses 2s complement, 32-bit representations of integers.
  2. Performs right shifts arithmetically.
  3. Has unpredictable behavior when shifting an integer by more
     than the word size.

【问题讨论】:

  • 签名溢出会导致未定义的行为。因此,如果您将 x=tmax 传递给您已经在第一条语句中导致 ub 的函数。

标签: c optimization


【解决方案1】:

具体原因很可能在1 &lt;&lt; 31。名义上,这将产生 231,但 231 无法在 32 位 int 中表示。在 C 2018 6.5.7 4 中,C 标准指定了&lt;&lt; 的行为,它表示这种情况下的行为未定义。

当优化被禁用时,编译器可能会生成一条处理器指令,该指令提供 1 个左 31 位。这将产生位模式 0x80000000,随后的指令将其解释为 -231

相比之下,启用优化后,优化软件会识别出1 &lt;&lt; 31 未定义并且不会为其生成移位指令。它可以用编译时值替换它。由于 C 标准未定义该行为,因此允许编译器为此使用任何值。例如,它可能使用零。 (由于没有定义整个行为,而不仅仅是结果,实际上允许编译器用任何东西替换程序的这一部分。它可以使用完全不同的指令或只是中止。)

您可以使用1u &lt;&lt; 31 开始解决此问题。这是因为 231 适合 unsigned int 类型。但是,将其分配给tmin 时会出现问题,因为tminint,并且该值仍然不适合int。但是,对于这种转换,行为是实现定义的,而不是未定义的。常见的 C 实现定义转换为模 232,这意味着赋值将在 tmin 中存储 -231。但是,另一种方法是将tminint 更改为unsigned int(也可以写成unsigned),然后使用无符号整数。这将给出完全定义的行为,而不是未定义或实现定义的行为,除非假设 int 宽度为 32 位。

另一个问题是x + 1。当xINT_MAX 时,就会溢出。这可能不是您观察到的行为的原因,因为常见的编译器只是简单地包装了结果。尽管如此,也可以通过使用x + 1u 并将possible_tmin 的类型更改为unsigned 来进行类似的纠正。

也就是说,可以使用return ! (x ^ ~0u &gt;&gt; 1); 计算所需的结果。这将零作为unsigned int,对其进行补码以产生所有 1 位,并将其右移一位,这给出了单个 0 位,然后是所有 1 位。这就是INT_MAX 的值,无论int 的宽度如何,它都能正常工作。然后这是与x 异或。当且仅当x 也是INT_MAX 时,其结果全为零。然后! 要么将该零更改为 1,要么将一个非零值更改为 0。

【讨论】:

  • 你好埃里克,谢谢你的回答。我更新了我的帖子,除了int,还有其他规则禁止使用任何其他数据类型,我们可以假设还包括文字后缀U。我做了更多的测试,1u &lt;&lt; 31 甚至来自limit.hINT_MIN 都不能解决问题。目前看来问题源于x+1 表达式,它导致未定义的行为。使用x+1u 确实解决了这个问题。当我回到指令中时,我意识到我无论如何都不能使用移位运算符来解决这个问题,所以整个想法就崩溃了。
【解决方案2】:

将变量的类型从 int 更改为 unsigned int(或只是 unsigned),因为带符号值的按位运算会导致未定义的行为。

【讨论】:

  • 仅仅改变声明中的类型是不够的。 1 &lt;&lt; 31 未定义。
  • 忘记了,谢谢,还需要把1 &lt;&lt; 31换成1U &lt;&lt; 31
【解决方案3】:

@Voo 做出了正确的观察,x+1 创建了一个未定义的行为,起初并不明显,因为 printf 调用没有显示任何奇怪的事情发生。

【讨论】:

    猜你喜欢
    • 2014-02-16
    • 2017-02-05
    • 1970-01-01
    • 2012-05-08
    • 1970-01-01
    • 2011-09-22
    • 1970-01-01
    • 2016-06-18
    • 1970-01-01
    相关资源
    最近更新 更多