Странное C ++ логическое поведение приведения (true! = True) - PullRequest
10 голосов
/ 10 августа 2009

Просто прочитайте на внутренней ветке университета:

#include <iostream>
using namespace std;

union zt
{
 bool b;
 int i;
};

int main()
{
 zt w;
 bool a,b;
 a=1;
 b=2;
 cerr<<(bool)2<<static_cast<bool>(2)<<endl;                      //11
  cerr<<a<<b<<(a==b)<<endl;                                      //111
 w.i=2;
 int q=w.b;
 cerr<<(bool)q<<q<<w.b<<((bool)((int)w.b))<<w.i<<(w.b==a)<<endl; //122220
 cerr<<((w.b==a)?'T':'F')<<endl;                                 //F
}

Так что a, b и w.b все объявлены как bool. a назначается 1, b назначается 2, а внутреннее представление w.b изменяется на 2 (с использованием union).

Таким образом, все a, b и w.b будут true, но a и w.b не будут равны, поэтому это может означать, что вселенная сломана (true!=true)

Я знаю, что эта проблема скорее теоретическая, чем практическая (ради программиста не хочет менять внутреннее представление bool), но вот вопросы:

  1. Это хорошо? (это было протестировано с g ++ 4.3.3) Я имею в виду, должен ли компилятор знать, что во время логического сравнения любое ненулевое значение может означать true?
  2. Знаете ли вы какой-либо случай, когда этот угловой случай может стать реальной проблемой? (Например, при загрузке двоичных данных из потока)

EDIT:

Три вещи:

  1. bool и int имеют разные размеры, это нормально. Но что, если я использую char вместо int. Или когда sizeof(bool)==sizeof(int)?

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

  3. Новый вопрос: действительно ли это неопределенное поведение? Если да, то почему? Если нет, то почему? Нет ли каких-либо допущений относительно операторов логического сравнения в спецификациях?

Ответы [ 8 ]

17 голосов
/ 10 августа 2009

Если вы читаете члена объединения, который отличается от последнего члена, который был написан, вы получаете неопределенное поведение. Написание элемента int и затем чтение элемента bool объединения может привести к чему-либо в любой последующий момент программы.

Единственное исключение - случаи, когда объединения представляют собой объединение структур, и все структуры содержат общую начальную последовательность, и в этом случае общая последовательность может быть прочитана.

9 голосов
/ 10 августа 2009
  1. Это хорошо? (это было протестировано с g ++ 4.3.3) Я имею в виду, должен ли компилятор знать, что во время логического сравнения любое ненулевое значение может означать true?

Любое целочисленное значение, отличное от нуля (или указатель, отличный от NULL), представляет собой значение true. Но при сравнении целых и bool перед сравнением bool преобразуется в int.

  1. Знаете ли вы случай, когда этот угловой случай может стать реальной проблемой? (Например при двоичной загрузке данных из потока)

Это всегда реальная проблема.

  1. Это нормально?

    Я не знаю, указывают ли спецификации что-либо об этом. Компилятор всегда может создать такой код: ((a! = 0) && (b! = 0)) || ((a == 0) && (b == 0)) при сравнении двух логических значений, хотя это может снизить производительность.

    На мой взгляд, это не ошибка, а неопределенное поведение. Хотя я думаю, что каждый разработчик должен сообщать пользователям, как в их реализации выполняются логические сравнения.

Если мы пойдем по последнему примеру кода, и a и b будут bool и установлены в true, присваивая 1 и 2 с уважением (Нет, 1 и 2 исчезают, теперь они просто верны).

Итак, сломав ваше выражение:

a!=0      // true (a converted to 1 because of auto-type conversion)
b!=0      // true (b converted to 1 because of auto-type conversion)

((a!=0) && (b!=0)) => (true && true)  // true ( no conversion done)

a==0      // false (a converted to 1 because of auto-type conversion)
b==0      // false (b converted to 1 because of auto-type conversion)

((a==0) && (b==0)) => (false && false) // false ( no conversion done)

((a!=0) && (b!=0)) || ((a==0) && (b==0)) => (true || false) => true

Так что я бы всегда ожидал, что приведенное выше выражение будет хорошо определенным и всегда верным

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

При сравнении bool с int bool сначала конвертируется в int, а затем сравнивается.

  1. Любой случай из реальной жизни

    Единственное, что приходит мне в голову, если кто-то читает двоичные данные из файла в структуру, в которой есть члены bool. Проблема может возникнуть, если файл был создан другой программой, написавшей 2 вместо 1 вместо bool (возможно, потому что он был написан на другом языке программирования).

    Но это может означать плохую практику программирования.

Запись данных в двоичном формате непереносима без знания.
Есть проблемы с размером каждого объекта.
Есть проблемы с представлением:

  • Целые числа (имеют порядковый номер)
  • Float (представление не определено ((обычно зависит от базового оборудования))
  • Bool (двоичное представление не определено стандартом)
  • Структура (отступы между элементами могут отличаться)

Со всем этим вам нужно знать базовое оборудование и компилятор. Разные компиляторы или разные версии компилятора или даже компилятор с разными флагами оптимизации могут иметь разное поведение для всего вышеперечисленного.

Проблема с Союзом

struct X
{
    int  a;
    bool b;
};

Поскольку люди упоминают, что пишут в 'a', а затем читают из 'b', оно не определено.
Почему: потому что мы не знаем, как 'a' или 'b' представлены на этом оборудовании. Запись в «а» заполнит биты в «а», но как это отразится на битах в «б». Если ваша система использовала 1 байт bool и 4 байта int с младшим байтом младшего байта в старшем байте старшей памяти, то запись 1 в 'a' приведет к 1 в 'b'. Но тогда как ваша реализация представляет собой bool? Является ли истина представленной 1 или 255? Что произойдет, если вы поставите 1 в 'b' и для всех других случаев использования true он использует 255?

Так что, если вы не понимаете и свое оборудование, и ваш компилятор, поведение будет неожиданным.

Таким образом, эти виды использования не определены, но не запрещены стандартом. Причина, по которой им это разрешено, заключается в том, что вы, возможно, провели исследование и обнаружили, что в вашей системе с этим конкретным компилятором вы можете выполнить некоторую простую оптимизацию, сделав эти предположения. Но имейте в виду, что любые изменения в предположениях нарушат ваш код.

Кроме того, при сравнении двух типов компилятор будет выполнять некоторые автоконвертации перед сравнением, помните, что оба типа преобразуются в один и тот же тип перед сравнением. Для сравнения между целыми числами и bool bool конвертируется в целое число, а затем сравнивается с другим целым числом (преобразование преобразует false в 0 и true в 1). Если оба объекта являются булевыми, тогда преобразование не требуется, и сравнение выполняется с использованием логической логики.

9 голосов
/ 10 августа 2009

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

int x = 5;
bool z = x; // automatic conversion here

Эквивалентный код, сгенерированный компилятором, будет выглядеть примерно так:

bool z = (x != 0) ? true : false;

Однако компилятор выполнит это преобразование только один раз. Было бы неразумно предполагать, что любой ненулевой битовый шаблон в переменной bool эквивалентен true, особенно для выполнения логических операций, таких как и . Полученный код сборки будет громоздким.

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

2 голосов
/ 10 августа 2009

Логическое значение - один байт, а целое число - четыре байта. Когда вы присваиваете целое число 2, четвертый байт имеет значение 2, но первый байт имеет значение 0. Если вы читаете логическое значение из объединения, он собирается захватить первый байт.

Редактировать: Д'Ох. Как отмечает Олег Жилин, это относится только к процессору с прямым порядком байтов. Спасибо за исправление.

1 голос
/ 10 августа 2009

Я верю, что то, что вы делаете, называется типом наказания: http://en.wikipedia.org/wiki/Type_punning

0 голосов
/ 10 августа 2009

Отвечая на поставленные вопросы, я думаю, что поведение в порядке и не должно быть проблемой в реальном мире. Поскольку у нас нет ^^ в C ++, я бы предложил! Bool ==! Bool в качестве безопасного метода сравнения bool.

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

0 голосов
/ 10 августа 2009

Просто, чтобы записать мою точку зрения:

  1. Это нормально?

    Я не знаю, указывают ли спецификации что-либо об этом. Компилятор всегда может создать такой код: ((a! = 0) && (b! = 0)) || ((a == 0) && (b == 0)) при сравнении двух логических значений, хотя это может снизить производительность.

    На мой взгляд, это не ошибка, а неопределенное поведение. Хотя я думаю, что каждый разработчик должен сообщать пользователям, как в их реализации выполняются логические сравнения.

  2. Любой случай из реальной жизни

    Единственное, что приходит мне в голову, если кто-то читает двоичные данные из файла в структуру, в которой есть члены bool. Проблема может возникнуть, если файл был создан другой программой, написавшей 2 вместо 1 вместо bool (возможно, потому что он был написан на другом языке программирования).

    Но это может означать плохую практику программирования.

Еще одно: во встроенных системах эта ошибка может быть большей проблемой, чем в «нормальной» системе, потому что программисты обычно делают больше «бит-магии», чтобы выполнить работу.

0 голосов
/ 10 августа 2009

Хм странно, я получаю другой вывод с кодовой панели:

11
111
122222
Т

Код также кажется мне правильным, может быть, это ошибка компилятора?
Смотрите здесь

...