为什么我在这里收到“假设没有发生有符号整数溢出”?

问题描述 投票:0回答:2

有问题的代码(godbolt

#include <stdbool.h>
void do_work(void);

typedef struct task {
    int timer;
    void (*job)(void);
} task_t;

typedef struct task_list {
    task_t *tasks;
    int length;
} task_list_t;

void test(task_list_t *task_list)
{
    for (int i = 0; i < task_list->length; i++)
    {
        task_t *task = &task_list->tasks[i];
        __attribute__((assume(task->timer > 0)));
        task->timer--;
        bool timer_finished = task->timer <= 0;
        if (timer_finished)
            task->job();
    }
}

我得到以下输出:

<source>: In function 'test':
<source>:25:1: warning: assuming signed overflow does not occur when changing X +- C1 cmp C2 to X cmp C2 -+ C1 [-Wstrict-overflow]
   25 | }
      | ^
<source>:25:1: warning: assuming signed overflow does not occur when changing X +- C1 cmp C2 to X cmp C2 -+ C1 [-Wstrict-overflow]
Compiler returned: 0

假设我无法将

task_t::timer
更改为无符号类型。为什么假设被忽略了?或者我从根本上误解了这个警告?

c gcc
2个回答
2
投票

您收到的警告

warning: assuming signed overflow does not occur [...] [-Wstrict-overflow]
是由于编译器让您知道它假设您的整数变量task_t::timer在递减时永远不会是INT_MIN(
task->timer--;
)。 当变量已达到最小值时递减变量会导致上溢(或下溢),C 标准将其定义为未定义行为 (UB)。

编译器假设它不会溢出,以优化生成的机器代码。它发出警告是因为,在优化以下内容时:

bool timer_finished = task->timer <= 0;

它假设

task->timer
不会溢出。如果是这样,由于 UB 的原因,比较可能会出现意想不到的结果。

__attribute__((assume))
帮助编译器理解特定点的某些属性,但它不会影响有关代码中整数溢出的假设。编译器仍然遵循 C 标准,这允许它在有符号溢出为 UB 的假设下进行优化。

要抑制警告,您需要向编译器明确表示,当可能出现有符号溢出时,永远不会执行比较,例如:

if (task->timer > 0) {
    task->timer--;
    bool timer_finished = task->timer <= 0;
    if (timer_finished)
        task->job();
}

这确保了`task->timer仅在为正数时递减,避免潜在的下溢问题并使编译器更高兴并且不会发出警告:)


1
投票

疯狂的猜测......但我想说,原因是在某个中间阶段,比较

task->timer <= 0
在递减之前被编译器移动,将其更改为
task->timer <= 1

如果您声明一个外部函数

void foo(void);
并在递减之后但在测试之前调用
foo()
,您会注意到警告消失了(在这种情况下,将递减操作传播到比较将是不合法的,因为
foo()
可能具有更改了字段值)。

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