有问题的代码(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
更改为无符号类型。为什么假设被忽略了?或者我从根本上误解了这个警告?
您收到的警告
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仅在为正数时递减,避免潜在的下溢问题并使编译器更高兴并且不会发出警告:)
疯狂的猜测......但我想说,原因是在某个中间阶段,比较
task->timer <= 0
在递减之前被编译器移动,将其更改为task->timer <= 1
。
如果您声明一个外部函数
void foo(void);
并在递减之后但在测试之前调用 foo()
,您会注意到警告消失了(在这种情况下,将递减操作传播到比较将是不合法的,因为 foo()
可能具有更改了字段值)。