Должно ли логическое значение быть усечено до true или false при назначении?


Я обнаружил разницу в значении, хранящемся в переменной bool (кстати, Visual-C++ и clang++), в том случае, когда сохраненное значение не является ни true, ни false (если оно было каким-то образом повреждено), и я не уверен, является ли это ошибкой Visual-C++ или это просто UB, который я должен игнорировать.

Возьмем следующий пример:

#include <cstdint>
#include <iostream>
#include <string>
#include <limits>
bool inLimits(bool const v)
{
    return (static_cast<std::int32_t>(v) >= static_cast<std::int32_t>(std::numeric_limits<bool>::min()) && static_cast<std::int32_t>(v) <= static_cast<std::int32_t>(std::numeric_limits<bool>::max()));
}
int main()
{
    bool b{ false };
    bool const* const pb = reinterpret_cast<bool const*>(&b);
    std::uint8_t * const pi = reinterpret_cast<std::uint8_t*>(&b);

    std::cout << "b: " << b << " pb: " << (*pb) << " pi: " << std::to_string(*pi) << std::endl;
    std::cout << "b is " << (inLimits(b) ? "" : "not ") << "in numeric limits for a bool" << std::endl;

    *pi = 3; // Simulate a bad cast during boolean creation
    bool const b2{ b };
    bool const b3{ *pb };

    std::cout << "b: " << b << " pb: " << (*pb) << " pi: " << std::to_string(*pi) << std::endl;
    std::cout << "b2: " << b2 << " b3: " << b3 << std::endl;

    std::cout << "b is " << (inLimits(b) ? "" : "not ") << "in numeric limits for a bool" << std::endl;
    std::cout << "b2 is " << (inLimits(b2) ? "" : "not ") << "in numeric limits for a bool" << std::endl;
    std::cout << "b3 is " << (inLimits(b3) ? "" : "not ") << "in numeric limits for a bool" << std::endl;

    return 0;
}

Это выход Visual-C++

b: 0 pb: 0 pi: 0
b is in numeric limits for a bool
b: 3 pb: 3 pi: 3
b2: 3 b3: 3
b is not in numeric limits for a bool
b2 is not in numeric limits for a bool
b3 is not in numeric limits for a bool

И это результат clang++

b: 0 pb: 0 pi: 0
b is in numeric limits for a bool
b: 1 pb: 1 pi: 3
b2: 1 b3: 1
b is in numeric limits for a bool
b2 is in numeric limits for a bool
b3 is in numeric limits for a bool

Похоже, что в clang++ есть проверка ограничений при построении новое логическое значение по значению,а также когда оно используется с оператором потока.

Должен ли я просто игнорировать это, или это ошибка, которая есть только в Visual-C++? Спасибо!

Редактировать: Для тех, кто не понимал цели примера, это была просто демонстрация для "имитации" повреждения памяти или ошибки в другой части кода, которая вызвала инициализацию логического значения с чем-то другим, чем true или false, независимо от двоичного представления bool.

(я был интересно, должен ли я защищать свой код от неправильного использования где-то еще, например, с помощью assert, но только если это поведение не UB)

Второе редактирование: Добавлен код numeric_limits.

4 3

4 ответа:

Стандарт не определяет, что такое представление значения bool. Компиляторы могут свободно создавать свои собственные спецификации.

Ваши доказательства говорят о том, что VC++ требует, чтобы true был представлен только как набор LSB, тогда как clang++ позволяет любому ненулевому представлению быть true.

Для VC++ ваш код вызывает неопределенное поведение в строке bool const b2{ b };, особенно когда он пытается прочитать значение из b. Биты, установленные в хранилище для b, не соответствуют значению b, и стандарт не определяет, что происходит в этой ситуации, поэтому это неопределенное поведение.

Когда происходит неопределенное поведение, нет никаких гарантий; все выходные данные программы бессмысленны. Вы не можете сделать никаких выводов на основе выходных инструкций, которые появляются после этой точки (или даже до нее, на самом деле).

"в случае, когда сохраненное значение не является ни истинным, ни ложным"

Почему вы думаете, что это так? C++ не ограничивает двоичное представление bool. В некоторых компиляторах true может быть представлено 00000011, а другие компиляторы могут выбрать представление false как 00000011.

Но на самом деле ни GCC, ни MSVC не используют этот битовый шаблон для любого значения bool. Это делает его действительно неопределенным поведением. УБ может не это ошибка компилятора. Ошибка там, где реализация не работает так, как должно, но UB конкретно означает, что любое фактическое поведение приемлемо.

Поскольку я действительно не нашел информацию о приведениях от указателя к bool (или эквиваленту) в стандарте C++ (если их использование определено), я не хотел публиковать это в качестве ответа. Но, с другой стороны, я могу также опубликовать его-он может быть разработан другими людьми.

Прежде всего, стандарт C++14 определяет bool как:

[основной.фундаментальный]

  1. значения типа bool являются либо истинными, либо ложными. [Примечание: нет подписанных, неподписанных, короткие или длинные типы или значения bool. - конечное Примечание] значения типа bool участвуют в интегральных акциях (4.5)

Поскольку он участвует в интегральных акциях, для него определяется следующая акция:

[усл.выпускной бал]

  1. prvalue типа bool может быть преобразовано в prvalue типа int, при этом false становится нулем, а true-единицей.

И, поскольку вы печатаете с std::ostream::operator<<, Для bool он определяется как образом:

[острим.вставки.арифметика]

  1. классы num_get и num_put обрабатывают зависящее от локали числовое форматирование и синтаксический анализ.

Поскольку он использует num_put<> для фактического вывода, фрагмент его, связанный с bool выводом, определяется как:

[фасет.номер.класть.виртуалы]

  1. Если (ул.флаги() & ios_base::boolalpha) == 0 возвращает do_put(уходит, стр заливки (инт)Валь)

С тех пор как ты не используйте boolalpha в показанном примере-должны применяться типичные интегральные правила продвижения (описанные выше).

Кроме того, я до сих пор не могу объяснить, почему std::to_string(*pi) после *pi = 3 все еще печатает 3 в обоих случаях, но это может быть как-то связано с:

[expr.интерпретировать.бросок]

  1. [Примечание: сопоставление, выполненное reinterpret_cast, может привести или не привести к представлению, отличному от исходного значения.- Конечная нота]

Не уверен, что это поможет, но g++ демонстрирует то же поведение, что и Visual-C++.

Вот результат, который я получил:

b: 0 pb: 0 pi: 0
b: 3 pb: 3 pi: 3
b2: 3 b3: 3

Из того, что я понимаю (я эксперт по компиляторам c++), reinterpret_cast инструктирует компилятор рассматривать коллекцию битов как новый тип. Поэтому, когда вы говорите компилятору, чтобы он переинтерпретировал адрес булева как 8-битное целое число, он, по сути, преобразует исходное булево число в 8-битное целое число (если это имеет смысл).

Так что если мой интерпретация правильна (это не так), возможно, это "ошибка" в clang++, а не Visual или g++. reinterpret_cast не очень хорошо поддерживается между компиляторами, поэтому это поведение определенно стоит отметить при принятии решения о том, что использовать, если это необходимо по какой-либо причине.

Правка:

Я только что понял, что это не объясняет, почему b2 и b3 также являются 3 (не булевыми). Я не думаю, что имеет смысл рассматривать новые булевы как 8-битные целые числа, независимо от reinterpret_cast, так что возьмите это за то, что это стоит от парня с 1 повторением :)