В моем коде есть функция, которая выглядит примерно так:
bool foo(ObjectType& object){
//code
const float result = object.float_data + object.bool_data * integer_data;
//code
}
При поиске ошибки я обнаружил, что result иногда имеет неверные значения, и причина в том, что иногда bool * integer вычисляется как 255*integer вместо 1*integer.
C++ говорит, что в целочисленном контексте bool преобразуется либо в ноль, либо в единицу, поэтому я этого не понимаю. Я умножаю на bool другие части кода, и он отлично работает. Тоже случайный: иногда конвертируется в 1, иногда в 255. Отладчик также показывает true или 255 соответственно. Когда эта ошибка возникает, это всегда происходит в этом исполнении. Перекомпиляция кода не имеет никакого эффекта, это все равно происходит случайным образом.
Вы пробовали использовать assert(object.bool_data && object.bool_data == 1), так что посмотрите, действительно ли вы столкнулись с этой ошибкой, или кто-то другой делает какие-то странные вещи?
Можете ли вы опубликовать код (включая функцию main), который воспроизводит точную проблему?
пожалуйста, предоставьте объявления переменных вместо венгерской нотации
Это может быть проявление UB где-то еще в коде.
@HolyBlackCat В этом есть смысл. Однако это было бы очень сложно отладить.
@rubenvb Нет, программа 50К +, но я постараюсь воспроизвести ее с помощью программы меньшего размера.
@ user463035818 Это простые встроенные типы float, bool, int. Никаких шаблонов, определений типов или литералов.
@Newline - это то, о чем я догадывался, но я предпочитаю видеть код. Прочтите, пожалуйста, о минимальный воспроизводимый пример
@Someprogrammerdude Я считаю, что дамп QA невероятно груб для этого вопроса. 1. Вопрос ясен и краток («почему это поведение, гарантированное стандартом C++, не происходит в моем коде») и 2. задающий вопрос явно знает, как отлаживать программу - выводы прямо в вопросе. Если ваша точка зрения - «можете ли вы воспроизвести это в программе меньшего размера», просто скажите об этом. Сбрасывать все ссылки «научись спрашивать» на кого-то только потому, что они не потратили часы (или не преуспели), пытаясь воспроизвести проблему, хотя может быть простой ответ, - это ужасно.
@MaxLanghof Я не собираюсь показаться грубым, а просто поприветствовать нового пользователя на сайте и дать хорошие ссылки, чтобы прочитать о том, как стать продуктивным участником и как писать хорошие вопросы. И последний момент очень важен для новичков, потому что даже если этот вопрос не очень плох для первого вопроса от нового участника, на него также невозможно ответить в его нынешней форме. Все, что мы действительно можем сделать, - это предложить возможные обходные пути и бессмысленные догадки и предположения. Чего мы не можем сделать, так это дать окончательный ответ о том, почему у ОП есть эта проблема.
@Someprogrammerdude «просто чтобы поприветствовать нового пользователя на сайте и дать хорошие ссылки, чтобы прочитать о том, как стать продуктивным участником и как писать хорошие вопросы» -> Сайт уже делает это на каждом углу. Кроме того, idownvotedbecau.se (включая «прочтите все»), безусловно, является противоположностью приветствия, поэтому упреждающий отказ от него на достойных вопросах приносит больше вреда, чем пользы. Имейте в виду, я не имею в виду, что вы пытаетесь показаться грубым или что у вас плохие намерения, просто это выглядит (очень) недружелюбно. Я бы посоветовал сделать ссылку на MCVE и покончить с этим, если вас это беспокоит.
@Someprogrammerdude Я бы добавил, что да, в этом конкретном примере это не вопрос, на который можно ответить, но есть десятки причин, по которым хороший ответ мог бы быть доступен без MCVE. Это может быть известная ошибка GCC, неправильное понимание стандарта и т. д. В таких случаях для автора запроса было бы пустой тратой времени потратить часы или дни на создание MCVE.
«иногда целое число bool * вычисляется как 255 * целое вместо 1 * целое» Этого не может произойти в хорошо управляемой программе, поэтому либо где-то есть UB, либо в вашем компиляторе есть ошибка. Можете ли вы произвести минимальный воспроизводимый пример?





Согласно C++17 7.14 Boolean conversions, принуждение не-логического значения к логическому является частью стандартного процесса преобразования, то есть фактически выполняется при присвоении некоторого значения логическому:
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type
bool. A zero value, null pointer value, or null member pointer value is converted tofalse; any other value is converted totrue. For direct-initialization (11.6), a prvalue of typestd::nullptr_tcan be converted to a prvalue of typebool; the resulting value isfalse.
нет гарантирует, что логическое значение будет равно нулю или единице, если, например, вы инициализируете его не или инициализируете его таким образом, что оно будет рассматриваться как не логическое:
void someFunction() {
bool xyzzy; // Set to some arbitrary value.
memcpy(&xyzzy, "x", 1); // Very contrived, wouldn't pass my
// own code review standards :-)
}
Итак, вещь первый, на которую я буду смотреть, гарантирует, что вы инициализируете / назначаете являются правильно.
Фактически, я очень люблю редко в настоящее время создаю переменную без явной инициализации ее чем-то. Даже если позже он будет изменен перед использованием, я бы предпочел полагаться на компилятор, который это выяснит и оптимизирует, если сочтет это полезным.
Но вы можете исправить это, просто используя логические значения, как они были задуманы. Другими словами, скорее как хранилища истины, чем оценивать их как целостные ценности. Попробуйте вместо этого:
const float result = object.float_data + (object.bool_data ? integer_data : 0);
Это будет работать, если bool_data:
integer_data, добавленного к вашему float_result); илиИсправить это не проблема, я хочу знать, почему это происходит. Это ошибка компилятора или что-то не так с IDE или моим кодом.
Ваша IDE не создает никакого кода, поэтому она не может нести за это ответственность, а ваш компилятор делает.
@paxdiabolo: подобный код обычно используется в автономном программировании, что очень часто встречается в коде графического процессора.
Или исправьте это, не используя здесь bool. Вместо этого используйте значение int 0 или 1.
@Khouri: тогда у вас точно такая же проблема, как и у OP. Вы не должны использовать int для чего-то, что по сути является истинным / ложным значением. Если вы это сделаете, и кто-то установит его на 2, ваши расчеты будут ошибочными.
Это не тоже самое. Подсчет, который может иметь разумные значения 0 или 1 в определенном контексте, отличается от предположения, какие значения компилятор считает true и false.
За исключением того, что здесь вопрос явно указывает bool, а не какой-то счетчик, который может быть неявно преобразован в bool. Похоже, вы комментируете вопрос разные :-)
Я думаю, вам следует использовать следующий код, а затем выполнить его
bool foo(ObjectType& object){
//code
const float result = object.float_data + int(object.bool_data) * int(integer_data);
//code
}
Я не думаю, что это будет иметь значение
Это могло иметь значение. Но поскольку код OP, вероятно, сломался из-за UB, мы не можем быть уверены.
b * i и int(b) * int(i) делают то же самое.
Решено. Значение типа bool не было инициализировано, поэтому иногда оно содержало 255 (чушь). Я предположил, что bool преобразуется в 0 или 1, когда он используется в целочисленном контексте, вместо этого он преобразуется в 0 или 1, когда он устанавливается из значения. (Это была полностью моя ошибка, извините за зря потраченное время.)
Подводя итог с помощью кода:
float float_data = 50.0f;
int integer_data = 30;
bool bool_data;
float result;
bool_data = 255; //OK
reinterpret_cast<int&>( bool_data) = 250; //NOT OK
result = float_data + (bool_data ? integer_data : 0); //OK
result = float_data + bool_data * integer_data; //NOT OK.
result = float_data + !!bool_data * integer_data; //NOT OK.
result = float_data + (bool_data == true) * integer_data; //NOT OK.
"ОК", что означает, что это все еще неверно, но не менее 0 или 1.
Хех, как уже упоминалось в моем ответе, вы столкнулись с точным примером стандарт предусматривает ...
Вы должны проверить, где установлен bool. Примечательно, что стандарт C++ гарантирует, что нет любая память, доступная как значение bool, будет преобразована в 1, если память не была в точности 0 - он только гарантирует, что bool со значением true будет преобразован в 1. Как упомянуто здесь, bool может иметь значение, которое не является ни true, ни false, как следствие неопределенное поведение. Это может показаться очевидным (все-таки UB), но это также удивительно (видимо, даже по стандартным авторским меркам).
Вот практическая демонстрация этого с использованием memcpy:
#pragma pack(1)
struct ObjectType
{
float float_data = -3.0f;
bool bool_data = false;
int integer_data = 2;
};
volatile unsigned char x = 7;
float test()
{
// Don't actually do this, it is for demonstration only.
std::array<unsigned char, sizeof(ObjectType)> data = { 0, 0, 0, 0, /**/ x, /**/ 2, 0, 0, 0 };
ObjectType obj;
memcpy(&obj, data.data(), sizeof(obj));
return foo(obj);
}
http://coliru.stacked-crooked.com/a/0221a82a6d35e18b <- оценка времени выполнения дает 14
http://coliru.stacked-crooked.com/a/982ff8e4d7503f08 <- оценка времени компиляции дает 2
Действительно, проверка двоичного файла показывает, что foo буквально интерпретирует bool как целое число и умножается на него для gcc. Я бы сделал вывод, что gcc достигает стандартного соответствия, только когда-либо сохраняя 1 или 0 всякий раз, когда он сохраняется в bool, таким образом, поведение как будтоtrue всегда преобразуется только в 1 (до тех пор, пока вы никогда не принудительно переводите bool в состояние, отличное от true или false. ).
Спасибо! Имеет смысл, что gcc делает это по соображениям производительности, если он не инициализирован, тогда UB использует это значение, AFAIK.
Этот ответ очень сбивает с толку / вводит в заблуждение. Инициализированный bool всегда является именно true или false, который всегда продвигается в точности как 1 или 0. Это может быть неинициализированный, но не может быть ... 2.
@Barry Считаете ли вы пример memcpy в моем ответе случаем логического неинициализированный? Мне это могло показаться неправильным. И поскольку memcpy - это, по сути, единственный способ преобразования без наложения имен, например сетевой пакет для структурированных данных, теоретически вы можете столкнуться с этой проблемой при отправке пакетов, содержащих состояние bool, между различными системами.
@MaxLanghof Вы просто демонстрируете несвязанное неопределенное поведение с очевидной проблемой OP. Да, сгенерированная сборка просто умножается на 7, потому что вы скопировали в нее недопустимое представление объекта. Это не меняет того факта, что согласно стандарту bool не может быть 7. Вы просто нарушили предварительные условия.
@Barry Дело было в том, чтобы попробовать то, что стандарт указывает на себя здесь. Очевидно, что UB - это UB, и это примечание, следовательно, технически избыточно, но тот факт, что примечание все еще там, демонстрирует, что даже стандартные авторы считали очень неинтуитивным следствием UB то, что bool не может быть ни true, ни false. Я отредактировал свой пост, чтобы прямо упомянуть, что для достижения этого состояния требуется UB.
Также прочтите этот контрольный список вопросов и все idownvotedbecau.se, чтобы узнать, почему ваш вопрос может быть отклонен. Наконец, пожалуйста, узнать, как отлаживать свои программы.