为什么clang会为使用-O1编译的c代码产生错误的结果,但不能使用-O0?

问题描述 投票:1回答:3

对于输入0xffffffff,以下c代码在没有优化的情况下工作正常,但在使用-O1编译时会产生错误的结果。其他编译选项是-g -m32 -Wall。代码在macOS 10.13.2中使用clang-900.0.39.2进行测试。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc < 2) return 1;

    char *endp;
    int x = (int)strtoll(argv[1], &endp, 0);

    int mask1 = 0x55555555;
    int mask2 = 0x33333333;
    int count = (x & mask1) + ((x >> 1) & mask1);

    int v1 = count >> 2;
    printf("v1 = %#010x\n", v1);

    int v2 = v1 & mask2;
    printf("v2 = %#010x\n", v2);

    return 0;
}

输入:0xffffffff

输出-O0 :(预期)

v1 = 0xeaaaaaaa

v2 = 0x22222222

输出-O1 :(错误)

v1 = 0x2aaaaaaa

v2 = 0x02222222

下面是“int v1 = count >> 2”行的反汇编指令使用-O0和-O1。

使用-O0:

sarl $ 0x2,%结果

使用-O1:

shrl $ 0x2,你是%

下面是“int v2 = v1&mask2;”行的反汇编指令使用-O0和-O1。

使用-O0:

andl -0x24(%ebp),%esi // - 0x24(%ebp)存储0x33333333

使用-O1:

andl $ 0x13333333,%esi //为什么优化会将0x33333333更改为0x13333333?

此外,如果x在本地设置为0xffffffff而不是从参数中获取其值,则代码将按预期工作,即使使用-O1也是如此。

P.S:代码是基于我对CS:APP课程@CMU的数据实验室解决方案的实验性内容。实验室要求学生实现一个函数,该函数计算int变量的1位数而不使用除int之外的任何类型。

c clang
3个回答
2
投票

正如几位评论者指出的那样,右移符号值的定义并不明确。

我将x的声明和初始化更改为

unsigned int x = (unsigned int)strtoll(argv[1], &endp, 0);

并在-O0和-O1下获得一致的结果。 (但在进行更改之前,我能够在MacOS下的clang下重现您的结果。)


2
投票

正如您所发现的,在尝试将0xffffffff4294967295)存储在int x(其中INT_MAX7fffffff2147483647)时,您提出了实现定义的行为。 C11 Standard §6.3.1.3 (draft n1570) - Signed and unsigned integers每当使用strtoll(或strtoull)(两个版本的1-l都没问题)并尝试将值存储为int时,您必须在使用演员表进行作业之前检查INT_MAX的结果。 (或者如果使用精确的宽度类型,对INT32_MAX,或UINT32_MAX为unsigned)

此外,在涉及位操作的情况下,您可以通过使用stdint.h中提供的精确宽度类型以及inttypes.h中提供的相关格式说明符来消除不确定性并确保可移植性。在这里,不需要使用签名的int。将所有值作为unsigned(或uint32_t)处理会更有意义。

例如,以下提供了输入的默认值,以避免在没有参数的情况下执行代码时调用未定义行为(您也可以简单地测试argc),将strtoll替换为使用strtoul,验证相关变量中的输入拟合在分配之前处理错误,如果没有,然后使用明确的确切类型,例如

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>

int main (int argc, char *argv[]) {

    uint64_t tmp = argc > 1 ? strtoul (argv[1], NULL, 0) : 0xffffffff;

    if (tmp > UINT32_MAX) {
        fprintf (stderr, "input exceeds UINT32_MAX.\n");
        return 1;
    }

    uint32_t x = (uint32_t)tmp,
        mask1 = 0x55555555,
        mask2 = 0x33333333,
        count = (x & mask1) + ((x >> 1) & mask1),
        v1 = count >> 2,
        v2 = v1 & mask2;

    printf("v1 = 0x%" PRIx32 "\n", v1);

    printf("v2 = 0x%" PRIx32 "\n", v2);

    return 0;
}

示例使用/输出

$ ./bin/masktst
v1 = 0x2aaaaaaa
v2 = 0x22222222

编译

$ gcc -Wall -Wextra -pedantic -std=gnu11 -Ofast -o bin/masktst masktst.c

仔细看看,如果您有其他问题,请告诉我。


-2
投票

这个说法:

int x = (int)strtoll(argv[1], &endp, 0);

导致签名溢出,这是未定义的行为。

(在我的系统上,结果是:-1431655766

结果值往往会从那里走下坡路:

变量:v1收到:-357913942

变量:v2收到:572662306

%x格式说明符仅适用于无符号变量

© www.soinside.com 2019 - 2024. All rights reserved.