#cpp #cpp11 #неопределенное_поведение
тут же должно быть 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); }
Ответы
Ответ 1
Проблема кроется в строке, расположенной в цикле: *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++;
Комментариев нет:
Отправить комментарий