Código em questão ( 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();
}
}
Recebo a seguinte saída:
<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
Digamos que eu não consiga mudar task_t::timer
para um tipo não assinado. Por que o assume está sendo ignorado? Ou estou entendendo mal o aviso?
O aviso que você está recebendo
warning: assuming signed overflow does not occur [...] [-Wstrict-overflow]
é porque o compilador está informando que ele está assumindo que sua variável inteira task_t::timer nunca será INT_MIN ao decrementar (task->timer--;
). Decrementar a variável quando ela já está em seu valor mínimo causaria um estouro (ou subfluxo), que o padrão C define como Comportamento Indefinido (UB).O compilador assume que não vai estourar para otimizar o código de máquina resultante. Ele está emitindo um aviso porque, ao otimizar o seguinte:
ele assume que
task->timer
não irá estourar. Se isso acontecesse, a comparação poderia se comportar de forma inesperada devido ao UB.O
__attribute__((assume))
ajuda o compilador a entender certas propriedades em um ponto específico, mas não afeta suposições sobre estouros de inteiros no código. O compilador ainda respeita o padrão C, o que permite otimizar com a suposição de que estouro assinado é UB.Para suprimir o aviso, você precisa deixar claro para o compilador que a comparação nunca será realizada quando um estouro assinado for possível, por exemplo:
Isso garante que `task->timer só diminua quando for positivo, evitando possíveis problemas de estouro e deixando o compilador mais feliz e não gerando um aviso :)
Chute maluco... mas eu diria que o motivo é que em algum estágio intermediário a comparação
task->timer <= 0
está sendo movida pelo compilador antes do decremento, alterando-a paratask->timer <= 1
.Se você declarar uma função externa
void foo(void);
e chamá-lafoo()
depois do decremento, mas antes do teste, notará que o aviso desaparece (nesse caso, propagar a operação de decremento para a comparação não seria legal, poisfoo()
poderia ter alterado o valor do campo).