C++ - ошибка преобразования Bool в int

В моем коде есть функция, которая выглядит примерно так:

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 соответственно. Когда эта ошибка возникает, это всегда происходит в этом исполнении. Перекомпиляция кода не имеет никакого эффекта, это все равно происходит случайным образом.

Также прочтите этот контрольный список вопросов и все idownvotedbecau.se, чтобы узнать, почему ваш вопрос может быть отклонен. Наконец, пожалуйста, узнать, как отлаживать свои программы.

Some programmer dude 20.08.2018 11:20

Вы пробовали использовать assert(object.bool_data && object.bool_data == 1), так что посмотрите, действительно ли вы столкнулись с этой ошибкой, или кто-то другой делает какие-то странные вещи?

hellow 20.08.2018 11:21

Можете ли вы опубликовать код (включая функцию main), который воспроизводит точную проблему?

rubenvb 20.08.2018 11:21

пожалуйста, предоставьте объявления переменных вместо венгерской нотации

463035818_is_not_a_number 20.08.2018 11:22

Это может быть проявление UB где-то еще в коде.

HolyBlackCat 20.08.2018 11:28

@HolyBlackCat В этом есть смысл. Однако это было бы очень сложно отладить.

Newline 20.08.2018 11:33

@rubenvb Нет, программа 50К +, но я постараюсь воспроизвести ее с помощью программы меньшего размера.

Newline 20.08.2018 11:37

@ user463035818 Это простые встроенные типы float, bool, int. Никаких шаблонов, определений типов или литералов.

Newline 20.08.2018 11:42

@Newline - это то, о чем я догадывался, но я предпочитаю видеть код. Прочтите, пожалуйста, о минимальный воспроизводимый пример

463035818_is_not_a_number 20.08.2018 11:45

@Someprogrammerdude Я считаю, что дамп QA невероятно груб для этого вопроса. 1. Вопрос ясен и краток («почему это поведение, гарантированное стандартом C++, не происходит в моем коде») и 2. задающий вопрос явно знает, как отлаживать программу - выводы прямо в вопросе. Если ваша точка зрения - «можете ли вы воспроизвести это в программе меньшего размера», просто скажите об этом. Сбрасывать все ссылки «научись спрашивать» на кого-то только потому, что они не потратили часы (или не преуспели), пытаясь воспроизвести проблему, хотя может быть простой ответ, - это ужасно.

Max Langhof 20.08.2018 11:48

@MaxLanghof Я не собираюсь показаться грубым, а просто поприветствовать нового пользователя на сайте и дать хорошие ссылки, чтобы прочитать о том, как стать продуктивным участником и как писать хорошие вопросы. И последний момент очень важен для новичков, потому что даже если этот вопрос не очень плох для первого вопроса от нового участника, на него также невозможно ответить в его нынешней форме. Все, что мы действительно можем сделать, - это предложить возможные обходные пути и бессмысленные догадки и предположения. Чего мы не можем сделать, так это дать окончательный ответ о том, почему у ОП есть эта проблема.

Some programmer dude 20.08.2018 12:08

@Someprogrammerdude «просто чтобы поприветствовать нового пользователя на сайте и дать хорошие ссылки, чтобы прочитать о том, как стать продуктивным участником и как писать хорошие вопросы» -> Сайт уже делает это на каждом углу. Кроме того, idownvotedbecau.se (включая «прочтите все»), безусловно, является противоположностью приветствия, поэтому упреждающий отказ от него на достойных вопросах приносит больше вреда, чем пользы. Имейте в виду, я не имею в виду, что вы пытаетесь показаться грубым или что у вас плохие намерения, просто это выглядит (очень) недружелюбно. Я бы посоветовал сделать ссылку на MCVE и покончить с этим, если вас это беспокоит.

Max Langhof 20.08.2018 12:50

@Someprogrammerdude Я бы добавил, что да, в этом конкретном примере это не вопрос, на который можно ответить, но есть десятки причин, по которым хороший ответ мог бы быть доступен без MCVE. Это может быть известная ошибка GCC, неправильное понимание стандарта и т. д. В таких случаях для автора запроса было бы пустой тратой времени потратить часы или дни на создание MCVE.

Max Langhof 20.08.2018 12:58

«иногда целое число bool * вычисляется как 255 * целое вместо 1 * целое» Этого не может произойти в хорошо управляемой программе, поэтому либо где-то есть UB, либо в вашем компиляторе есть ошибка. Можете ли вы произвести минимальный воспроизводимый пример?

n. 1.8e9-where's-my-share m. 20.08.2018 13:28
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
14
1 087
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Согласно 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 to false; any other value is converted to true. For direct-initialization (11.6), a prvalue of type std::nullptr_t can be converted to a prvalue of type bool; the resulting value is false.

нет гарантирует, что логическое значение будет равно нулю или единице, если, например, вы инициализируете его не или инициализируете его таким образом, что оно будет рассматриваться как не логическое:

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 или моим кодом.

Newline 20.08.2018 11:28

Ваша IDE не создает никакого кода, поэтому она не может нести за это ответственность, а ваш компилятор делает.

hellow 20.08.2018 11:29

@paxdiabolo: подобный код обычно используется в автономном программировании, что очень часто встречается в коде графического процессора.

datenwolf 20.08.2018 11:35

Или исправьте это, не используя здесь bool. Вместо этого используйте значение int 0 или 1.

Khouri Giordano 20.08.2018 15:48

@Khouri: тогда у вас точно такая же проблема, как и у OP. Вы не должны использовать int для чего-то, что по сути является истинным / ложным значением. Если вы это сделаете, и кто-то установит его на 2, ваши расчеты будут ошибочными.

paxdiablo 21.08.2018 03:02

Это не тоже самое. Подсчет, который может иметь разумные значения 0 или 1 в определенном контексте, отличается от предположения, какие значения компилятор считает true и false.

Khouri Giordano 21.08.2018 03:43

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

paxdiablo 21.08.2018 09:09

Я думаю, вам следует использовать следующий код, а затем выполнить его

bool foo(ObjectType& object){
    //code
    const float result = object.float_data + int(object.bool_data) * int(integer_data);
    //code
}

Я не думаю, что это будет иметь значение

Alan Birtles 20.08.2018 11:41

Это могло иметь значение. Но поскольку код OP, вероятно, сломался из-за UB, мы не можем быть уверены.

HolyBlackCat 20.08.2018 11:47
b * i и int(b) * int(i) делают то же самое.
Barry 20.08.2018 18:40

Решено. Значение типа 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.

Хех, как уже упоминалось в моем ответе, вы столкнулись с точным примером стандарт предусматривает ...

Max Langhof 20.08.2018 13:21
Ответ принят как подходящий

Вы должны проверить, где установлен 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.

Newline 20.08.2018 13:34

Этот ответ очень сбивает с толку / вводит в заблуждение. Инициализированный bool всегда является именно true или false, который всегда продвигается в точности как 1 или 0. Это может быть неинициализированный, но не может быть ... 2.

Barry 20.08.2018 18:38

@Barry Считаете ли вы пример memcpy в моем ответе случаем логического неинициализированный? Мне это могло показаться неправильным. И поскольку memcpy - это, по сути, единственный способ преобразования без наложения имен, например сетевой пакет для структурированных данных, теоретически вы можете столкнуться с этой проблемой при отправке пакетов, содержащих состояние bool, между различными системами.

Max Langhof 20.08.2018 18:45

@MaxLanghof Вы просто демонстрируете несвязанное неопределенное поведение с очевидной проблемой OP. Да, сгенерированная сборка просто умножается на 7, потому что вы скопировали в нее недопустимое представление объекта. Это не меняет того факта, что согласно стандарту bool не может быть 7. Вы просто нарушили предварительные условия.

Barry 20.08.2018 19:01

@Barry Дело было в том, чтобы попробовать то, что стандарт указывает на себя здесь. Очевидно, что UB - это UB, и это примечание, следовательно, технически избыточно, но тот факт, что примечание все еще там, демонстрирует, что даже стандартные авторы считали очень неинтуитивным следствием UB то, что bool не может быть ни true, ни false. Я отредактировал свой пост, чтобы прямо упомянуть, что для достижения этого состояния требуется UB.

Max Langhof 20.08.2018 19:09

Другие вопросы по теме