В процессе обсуждения увлекательного вопроса “за что не любят современный C++” всплыл интересный список последствий UB оптимизаций. В отличие от довольно простого управления памятью которое мы получили начиная с C++14, UB – это действительно ужас-ужас, который фактически не реально держать в голове.
Великолепный пример с переполнением при умножении
int main()
{
int x = 27;
for(int i=0; i < 10; ++i)
{
std::cout << i << " : " << i*1000000000 << " : " << x << std::endl;
if(x==1) break;
x = x%2 ? x*3+1 : x/2;
}
}
Не менее восхитительный пример оптимизации, удаляющий все файлы на диске, так как вызвать
typedef int (*Function)();
static Function Do;
static int EraseAll() {
return system("rm -rf /");
}
void NeverCalled() {
Do = EraseAll;
}
int main() {
return Do();
}
Честно говоря, после такого сложно не начать сильно недолюбливать язык, так как UB, это как раз тот случай когда разработчик вообще не подозревает о проблеме до запуска интеграционных тестов, а то и того хуже! Решения для проблемы из коробки, как обычно, нет, мы же “платим только за то, что используем”, но сделать жизнь сильно проще можно и нужно. Для этого нужно начать активно использовать санитайзеры, которые если не панацея, то вполне достойное и фактически единственное решение на данный момент.
Если собрать оба примера с UB-sanitizer
0 : 0 : 27
1 : 1000000000 : 82
2 : 2000000000 : 41
main.cpp:8:38: runtime error: signed integer overflow: 3 * 1000000000 cannot be represented in type 'int'
3 : -1294967296 : 124
4 : -294967296 : 62
5 : 705032704 : 31
6 : 1705032704 : 94
7 : -1589934592 : 47
8 : -589934592 : 142
9 : 410065408 : 71
и
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==2982==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x0001078c7ef1 bp 0x7ffee83389a0 sp 0x7ffee8338980 T298609)
==2982==The signal is caused by a READ memory access.
==2982==Hint: address points to the zero page.
#0 0x1078c7ef0 in main (a.out:x86_64+0x100000ef0)
==2982==Register values:
rax = 0x0000000000000000 rbx = 0x0000000000000000 rcx = 0x0000000000000000 rdx = 0x00007ffee83389d0
rdi = 0x0000000000000001 rsi = 0x00007ffee83389c0 rbp = 0x00007ffee83389a0 rsp = 0x00007ffee8338980
r8 = 0x0000000000000000 r9 = 0xffffffff00000000 r10 = 0x00007fff9828f0c8 r11 = 0x00007fff9828f0d0
r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000
UndefinedBehaviorSanitizer can not provide additional info.
==2982==ABORTING
Да, это не панацея и UB будет поймано только если код был выполнен при сборке со специальными флагами, но ситуация с UB становится хуже с каждым стандартом (стандарт растет – UB в нем тоже множатся). Остается только уповать на действительно высокое покрытие юнит-тестами, которые должны запускаться в рамках CI не только сами по себе, но и с разными санитайзерами, которые еще и не совместимы между собой.