Действительно ли этот код не определен, как указывает Clang?

Я включил -fsanitize=undefined в своем проекте, который использует Catch, библиотеку модульного тестирования. Одна строка из Catch была отмечена этим флагом как вызывающая неопределенное поведение. Мне удалось сделать изолированный пример:

#include <iomanip>
#include <sstream>

int main()
{
    std::ostringstream os; 
    os << "0x" << std::setfill('0') << std::hex;
}

Скомпилировано с:

clang++ -fsanitize=undefined main.cpp

Если я запускаю это, дается следующая печать:

/usr/bin/../lib64/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/ios_base.h:96:24: runtime error: load of value 4294967221, which is not a valid value for type 'std::_Ios_Fmtflags'
/usr/bin/../lib64/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/ios_base.h:76:67: runtime error: load of value 4294967221, which is not a valid value for type 'std::_Ios_Fmtflags'

Это происходит у меня на clang 3.6.0 и у друга с clang 3.4-1ubuntu3. У меня этого не происходит на gcc версии 4.9.2

Так что же здесь? Действительно ли этот код плох, или на стороне clang происходит что-то подозрительное?


person Tobias    schedule 08.05.2015    source источник
comment
Просто os << std::hex;, кажется, также воспроизводит проблему.   -  person dyp    schedule 08.05.2015
comment
stackoverflow.com/questions/20617788/ Возможно это?   -  person Baum mit Augen    schedule 08.05.2015
comment
Я хотел бы более описательное название, но мне трудно придумать его.   -  person Shafik Yaghmour    schedule 08.05.2015


Ответы (1)


Это ошибка в libstdc++ из cfe-dev ветка списка рассылки с заголовком -fsanitize=неопределенные и общие библиотеки говорит:

Это ошибка в libstdС++. Вы сможете обойти это с помощью файла черного списка дезинфицирующих средств, как только Уилл выпустит патч для этого, но на данный момент ручная фильтрация, вероятно, будет вашим лучшим вариантом.

Вот патч, чтобы это исправить; В ближайшие несколько дней я постараюсь добавить это в апстрим libstdc++. [...]

Как я отметил dyp в комментариях, нередко можно увидеть системы, в которых clang использует libstdc++, а не libc++, и если мы проверим это на Coliru явно использует libstdc++ через -stdlib=libstdc++, мы действительно можем воспроизвести проблему.

Следующий отчет об ошибке libstdc++: неверные значения перечисления, вычисленные оператором~ в ios_base.h освещает этот вопрос и говорит:

Перегруженные операторы, определенные для перечислений в ios_base.h, имеют следующую форму:

Enum operator~(Enum e) { return Enum(~static_cast<int>(e)); }

~ создает значения за пределами диапазона значений типа перечисления, поэтому возврат к типу Enum имеет неопределенное значение (см. [expr.static.cast]p10), и на практике он создает значение Enum за пределами диапазона представляемые значения для типа Enum, поэтому поведение не определено.

Для справки [expr.static.cast]p10 говорит:

Значение интегрального типа или типа перечисления может быть явно преобразовано в тип перечисления. Значение не изменяется, если исходное значение находится в пределах диапазона значений перечисления (7.2). В противном случае результирующее значение не указано (и может не находиться в этом диапазоне). Значение типа с плавающей запятой также может быть преобразовано в тип перечисления. Результирующее значение совпадает с преобразованием исходного значения в базовый тип перечисления (4.9), а затем в тип перечисления.

и, как говорит hvd, это формально неопределенное поведение, но Ричард указывает, что на практике это заканчивается неопределенным поведением.

Т.К. указывает, что это поведение было изменено с неопределенного на неопределенное с помощью DR 1766. : значения вне диапазона значений перечисления:

Хотя проблема 1094 поясняет, что значение выражения типа перечисления может не находиться в диапазоне значений перечисления после преобразования в тип перечисления (см. 5.2.9 [expr.static.cast], параграф 10), результат просто неопределенное значение. Это, вероятно, следует усилить, чтобы получить неопределенное поведение, в свете того факта, что неопределенное поведение делает выражение непостоянным. См. также 9.6 [class.bit], параграф 4.

Новая формулировка появляется в проекте стандарта в N4431.

person Shafik Yaghmour    schedule 08.05.2015
comment
О, действительно интересно. Очень плохо, что их фильтрация вручную устраняет возможность использования gdb для прерывания таких ошибок, чтобы получить трассировку стека для их исправления. - person Tobias; 08.05.2015
comment
Тот факт, что приведение дает неопределенное значение, означает, что поведение определено. Если бы поведение было неопределенным, стандарт сказал бы, что поведение не определено. (Тем не менее, это все еще бесполезное поведение, поэтому код все равно следует изменить.) - person ; 08.05.2015
comment
@hvd хорошо, что Ричард говорит, что in practice it produces an Enum value outside the range of representable values for the Enum type, so behavior is undefined к тому времени, когда дезинфицирующее средство увидит это, различие может быть недоступно. - person Shafik Yaghmour; 08.05.2015
comment
@ Тобиас, предположительно, используя libc++ вместо libsdc++, должен решить эту проблему. - person Shafik Yaghmour; 08.05.2015
comment
@hvd Это было сделано полностью неопределенным CWG 1766< /а>. - person T.C.; 08.05.2015