Страницы

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

пятница, 20 декабря 2019 г.

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

#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++;

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

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