首页 > 解决方案 > 启用优化时 C 程序的输出发生变化

问题描述

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

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

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

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

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

#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 << 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.

标签: coptimization

解决方案


具体原因很可能在1 << 31。名义上,这将产生 2 31,但 2 31不能用 32-bit 表示int。在 C 2018 6.5.7 4 中,C 标准指定了 的行为<<,它表示这种情况下的行为未定义。

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

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

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

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

也就是说,可以使用 计算所需的结果return ! (x ^ ~0u >> 1);。这将零作为一个unsigned int,对其进行补充以产生所有 1 位,并将其右移一位,这给出了单个 0 位,然后是所有 1 位。这就是INT_MAX值,无论int. 然后与 进行异或x。其结果全为零当且仅当xis also INT_MAX。然后!要么将该零更改为 1,要么将一个非零值更改为 0。


推荐阅读