Страницы

Поиск по вопросам

четверг, 1 ноября 2018 г.

Почему программа с UB у меня всегда работает правильно?

тут же должно быть UB
#include #include int main() { std::vector s={'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}; auto beg=s.begin(); while (beg!=s.end() && !isspace(*beg)) { *beg=toupper(*beg++); } for (auto i : s) std::cout << i << " "; return(0); }


Ответ

Проблема кроется в строке, расположенной в цикле:
*beg=toupper(*beg++);
До c++17 это выражение может приводить к неопределённому поведению из-за отсутствия регламентированного порядка при вычислении левой и правой частей от оператора присваивания. Т.е. возможны следующие ситуации:
Сначала вычисляется левая часть. Мы запоминаем указатель на символ. Далее выполняем toupper и инкрементируем указатель. Пишем заглавную букву в запомненный ранее адрес. В итоге получаем, вероятно то, что задумывалось:
H E L L O W O R L D Сначала вычисляется правая часть. Т.е. выполняется toupper для текущей буквы, указатель инкрементируется. Левая часть вычисляется уже на смещённом указателе и т.о. первый символ массива не изменяется вовсе и более того, мы пишем за концом вектора (явное UB). На консоли увидим
h H H H H H H H H H
По ссылкам на результаты работы можно заметить, что использовались версии gcc 6.3 и 7.1 соответственно. Если глянуть в список поддержки c++ фич gcc, то можно увидеть в частности такой пункт:
Refining Expression Evaluation Order for Idiomatic C++
со ссылкой на proposal P0145R3, реализованный в версии 7. Основной момент здесь в том, что порядок вычисления выражений по разные стороны от = теперь регламентирован, сначала вычисляется правая часть, потом левая. Т.е. как раз наш случай #2 начиная с с++17 единственно возможный путь.
При сравнении пунктов стандарта C++11 и черновика под номером 4687 можно заметить, что в разделе [expr.ass] появилось предложение:
The right operand is sequenced before the left operand.
Таким образом, представленный код содержит UB для c++17 и может содержать его в более старых стандартах, если реализации компиляторов будут выбирать описанную ветку #2. Но дело в том, что по стандарту до c++17 порядок этих вычислений не описан и даже чисто теоретически может быть разным на разных итерациях цикла. Поэтому гарантировать отсутствия UB в коде до c++17 нельзя.
Чтобы однозначно избежать проблем - достаточно обеспечить выполнение инкремента после увеличения регистра символа:
*beg=toupper(*beg); beg++;

Комментариев нет:

Отправить комментарий