avva: (Default)
[personal profile] avva
Интересная заметка о багах в коде на C, вызванных оптимизациями компилятора, GCC в данном случае. В последние годы наметился тренд в сторону того, что компиляторы агрессивно оптимизируют код, опираясь на то, что undefined behavior по стандарту "не может случиться", и если случается, то "все дозволено". Я привык уже к этому, но такой прямой и бесхитростный случай, как по ссылке, меня шокировал: прямо написано

if (i >= 0 && i < sizeof(tab)) {
...
}
И при том, что i типа signed int, компилятор заводит выполение внутрь этого блока с отрицательным значением i.

То, что происходит, как объясняется подробно по ссылке, это что i получает отрицательное значение вследствие переполнения: большое положительное i умножают на другое положительное число. При этом компилятор при анализе кода позволил себе предположить, что "undefined behavior никогда не происходит" - что буквально говоря ложь - и "доказал" себе, что i остается положительным после умножения; поэтому проверку i>=0 просто удалил, в скомпилированном коде ее нет.

Я понимаю причины, по которым компиляторам нужны такие оптимизации, но это заходит слишком далеко все же, на мой взгляд. Если есть эксплицитная проверка границ, она не должна быть нарушена из-за ошибки с переполнением перед этим; такие проверки как раз нужны программисту, чтобы не сделать чего-то слишком ужасного. Здесь это всего лишь доступ к невыделенной памяти, но можно представить ситуацию, при которой, скажем, есть какое-то окно "безопасных операций" в виртуальной таблице функций, и операция вызывается по индексу, доступом к этой таблице, после того, как проверено, что индекс попал в "безопасное окно". В общем, много чего можно натворить.

В обсуждении в HN поясняют, что любой из двух флагов -fwrapv и -fno-strict-overflow решает проблему; первый заставляет компилятор считать, что переполнение переменной возвращается циклически с другой стороны, как это обычно и происходит; второй не дает компилятору предположить это, но также не позволяет ему пользоваться undefined behavior в таких случаях. Мне понравилось замечание 10-летней давности в какой-то линкуксовской рассылке: "The kernel currently uses -fno-strict-overflow because -fwrapv was buggy in some versions of gcc. Unfortunately -fno-strict-overflow is also buggy in some other versions of gcc."

Да, насчет того, зачем все это компиляторам нужно - это хорошо объясняется в другой заметке другого автора. Если вкратце, то 32-битные int-переменные со знаком требуют особого ухода на 64-битных платформах, и когда они служат переменной цикла, который должен запускаться как можно быстрее, их труднее соптимизировать. Если компилятор может незаметно для программиста предположить, что переменная цикла на самом деле 64-битная, код выходит быстрее. А для этого нужно, чтобы никогда не происходило (32-битного) переполнения, или хотя бы чтобы компилятор мог это предположить.

Эти проблемы в свою очередь восходят к решению, принятому четверть века назад, использовать 32 бита для int на 64-битных платформах. Вот обсуждение этого вопроса и этого решения, написанные в то время. Они тогда считали, что 64-битные системы редки и "в обозримом будущем 32-битные компьютеры будут доминировать в мире", а кроме того для переменных циклов, индексов массивов итп. 32 бита во всех нормальных ситуациях хватает. Оба аргумента сомнительны, а то, что int должен быть самым естественным типом на данной платформе - очевидный контраргумент; впрочем, я не возьмусь с уверенностью утверждать, что поступили тогда неверно. Так или иначе, этот фарш не прокрутить назад, и плоды этого решения в виде 32-битных интов мы пожинаем и будем продолжать пожинать.

Date: 2022-11-29 05:55 pm (UTC)
epimorphisms_split: (Default)
From: [personal profile] epimorphisms_split

It's not that UB never happens. It happens. But when it happens, everything is allowed. No requirements on behaviour, even on behaviour before the UB. It is written in the standard.

I have seen this today:

int func() {       
    for (int i = 0; i < 10; i++) 
      std::cout << i << "\n";
}

This compiles to an infinite loop.

Date: 2022-11-29 06:14 pm (UTC)
straktor: benders (Default)
From: [personal profile] straktor
...and that's why modern C/C++ must not be used for anything requiring reliability

the standard is shitty, they must have ruled any UB to be a fatal compile error, period

Date: 2022-11-30 11:13 am (UTC)
From: [personal profile] ichthuss
I'm not 100% sure, but seems like predicting UB at compile time is pretty similar to halting problem, and is undecidable.

January 2026

S M T W T F S
    1 2 3
4 5 6 7 8 910
11121314151617
18192021222324
25262728293031

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 11th, 2026 08:16 pm
Powered by Dreamwidth Studios