Когда переменная bool не true и не false одновременно

Недавно читали код и наткнулись на примерно такой такой фрагмент.

bool *t = new bool[X][Y];
// много строк
switch (t[M][N])
{
case true:
        // много строк
        break;
case false:
        // много строк
        break;
default:
        // много строк
        break;
}

Сразу возникает вопрос: зачем нужна ветка default? Если переменная не равна true, то она равна false. Однокурсник сказал: «Для отладки». Думаю: что тут можно отлаживать? Но не всё так просто.

Запускаю. Выполняется код в ветке default. Самое первое предположение — забыли поставить break. Внимательно перечитываю код — всё в порядке. Запускаю в отладчике и замечаю, что выполняется сразу ветка default, а в ветку true или false даже не заходит. Решаю просмотреть значение переменной. Visual Studio 2012 запущенная на Windows 7 показывает 205. Допустим. Плюсы я стал учить недавно, поэтому решил провести небольшой опыт: создал булевую переменную и присвоил ей 205. Отрабатывает ветка true. Пока я размышлял над этой проблемой, однокурсник нашёл решение этой проблемы: оказывается, массив не был инициализирован. После инициализации проблема исчезла. Теперь у меня возник возник вопрос: неужели проблема в switch? Ради проверки я решил провести следующий опыт.

#include <iostream>
using namespace std;

int main() {
    bool *t = new bool[1];
    switch (t[0])
    {
    case true:
        cout << "true\n";
        break;
    case false:
        cout << "false\n";
        break;
    default:
        cout << "superposition\n";
        break;
    }
    if(t[0] == true) {
        cout << "true\n";
    } else if(t[0] == false) {
        cout << "false\n";
    } else {
        cout << "superposition\n";
    }
    if(t[0]) {
        cout << "true\n";
    } else if(!t[0]) {
        cout << "false\n";
    } else {
        cout << "superposition\n";
    }
    delete[] t;
    return 0;
}

Вариант с switch или первый вариант с if позволяет определить не инициализированную булевую переменную. Второй вариант с if никогда не заходит в третий вариант.

Как создать такую переменную Шрёдингера? Создайте динамический или статический массив. Используйте его перед инициализацией. А также, самое вкусное, то, что вы можете скопировать это неопределённое значение, даже в скалярную переменную. Где это может пригодится — решать вам.

Интересно также то, что если перед проверкой вставить:

char a = t[0];
t[0] = a;
то переменная наконец-то станет true. А если написать
bool a = t[0];
t[0] = a;


то ничего не изменится.

Позднее я повторил опыт на Visual Studio 2013 с 5 пакетом обновления запущенная на Windows 7. Там отладчик говорит, что неинициализированная переменная равна true, но несмотря на это в блок true не заходит. Также она говорит, что ветка default не нужна, так как все возможные метки уже заданы. Но несмотря на это заходит в блок default.

В Linux данная особенность не проявляется.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 50
  • +21

    "Undefined behavior" можете добавить в теги. Вообще странно, что его существование и разная обработка в зависимости от компилятора вас удивляет.
    http://en.cppreference.com/w/cpp/language/ub

    • +11
      Может быть, потому что switch() работает с целыми числами, а true и false заменяются typedef-ами на 1 и 0 при компиляции? В неинициализированной переменной может быть любой мусор. Условие if(!var) сработает только если значение 0 или false, а вот switch() уже нет, там нужно точное совпадение.
      • +1
        Условие if(!var) сработает только если значение 0 или false, а вот switch() уже нет, там нужно точное совпадение.

        Умный компилятор (например, GCC) пользуется знанием того, что bool-переменная может иметь значение либо true, либо false, и (в любой корректной программе) третьего не дано.
        • 0
          Может вы правы, у меня GCC не ругается и не предупреждает если записать
          bool var = 10;
          Возможно он сразу приводит ее к «true» дополнительной инструкцией. Но если в коде нет явной инициализации, то нет и приведения, и в ячейке хранится любое число. Неявное приведение к int в switch() видимо не настолько «умное» чтобы его преобразовать к true/false, а скорее всего лишние проверки не добавляются по соображениям производительности: в конце-концов, Undefined behaviour лежит на совести программиста, а не компилятора. Он может толкьо предупредить.
          • 0
            Возможно он сразу приводит ее к «true» дополнительной инструкцией
            «true» понятие относительное, смотря как использовать.
            Если if (var) то будет true и будет выполняться (if делает сравнение на 0)
            Если if (var == true) то будет false, ибо true обозначен как «1», а у нас в переменной 10
            • +1
              Нет, неправда. Проверьте сами и убедитесь.
              • +2
                Это если совсем старый C. Начиная с C99 bool это нативный тип и компилятор умеет к нему приводить, поэтому вашем примере if(var == true) проверка пройдет.
                • +1
                  Точно, проверил. Преобразует к единице на этапе инициализации. То есть bool var = 10 сделает var равной единице.

                  Вот ведь как в память врезалось…
                  • 0
                    В какой-то старой вижуал студии помню так и работало: если bool b = 10, то потом в ней хранилось именно это значение.
              • +1
                Даже не дополнительной инструкцией приводит, а сразу же кладёт туда 1.
                • +1
                  GCC для примера с if-ами вообще странный код генерирует. Во-первых, он выкидывает напрочь ветку default из switch и выводит false, потому что в первом case-е сравнивает булевскую переменную с 1, когда не совпадает (а там же мусор, так что не совпадает) — сразу переходит на cout<<«false\n»;. А в обоих ифах gcc тупо сравнивает булевскую переменную с 0 и довольный выводит true. Ассемблерный код для if(t[0]) и if(t[0]==true) абсолютно одинаковый:
                  0x80486e8 <+0x0048> 80 3e 00 cmpb $0x0,(%esi)
                  0x80486eb <+0x004b> 75 43 jne 0x8048730 <main()+144>
                  Подозреваю, что MVC для switch-а честно сравнивает переменную сначала с 1, потом с 0 и в результате попадает в default.
                  А вот при явном присвоении «bool var=10;» gcc еще при компиляции преобразует 10 в true (то есть в 1), что в отладке видно.
                  • 0

                    В gcc с включенной оптимизацией компилируете?

                    • 0
                      Таки да, забыл выключить. Теперь для switch-а результат «superposition» и bool сравнивается как int, с 1 и 0, для if-ов по прежнему true. Да и ассемблерный код для них все равно одинаковый. В общем-то логично, что gcc if(t[0]) и if(t[0]==true) одинаково воспринимает, так же как и if(!t[0]) и if(t[0]==false).
              • +1
                это странно. Я думал, там сразу jz и jnz используются, без сравнения.
              • +2
                Замените bool на int и подставьте в условие вместо true "== 0" и вместо false "== 1" и всё станет на свои места.

                Сам по себе bool не существует, это, если не ошибаюсь, int, а сравнение используется только с нулём или единицей. Если его не инициализировать, то там может быть любая отличная от них ересь.
                • 0
                  bool по стандарту char
                  • 0
                    --
                    • +4
                      bool, по стандарту, это bool.
                      • 0
                        Он имел ввиду размер переменной типа `bool`. То бишь в размер машинного слова и на дефолтных архитектурах может принимать значения от 0 до 255.
                        • 0
                          А где в стандарте указано, что sizeof(char) == sizeof(bool)?
                          • +1
                            Там наоборот подчёркивается, что sizeof(bool) не стандартизован.
                            • 0
                              Это не стандартом диктуется же, а архитектурой. Исполнение команд размером меньше машинного слова вроде как вообще не предусмотрено ибо неэффективно. По крайней мере на современных архитектурах. Если не ошибась даже вроде есть механизмы обработки сразу нескольких булевых значений хранящихся в одном слове.
                              • 0
                                Однако, слово может быть 32/64 бита (внутри). А bool в пакованных структурах компилятор вроде как приводит к char. В не пакованных хз, да и надо ли оно вникать…
                                • +2
                                  В не пакованных хз, да и надо ли оно вникать…

                                  Там ничего сложного, и сто раз обсасывалось, в том числе и на Хабре.
                      • 0
                        подставьте в условие вместо true "== 0" и вместо false "== 1"

                        Разве не наоборот? Вместо true "== 1" и вместо false "== 0".

                        • 0
                          Наоборот естественно… спешил.
                      • +6

                        С UB бывают всякие «весёлые» эффекты. Например, null reference может быть одновременно == 0 и != 0, может быть равна другой такой же ссылке или не равна, в зависимости от версии компилятора и флагов оптимизации: пришлось рефакторить всю систему обработки ошибок. Простое правило: если вы полагаетесь на UB, вы добавляете в код немного святого рандома. Каждый компилятор вправе превратить этот код во что угодно, и он превратит. Garbage in => garbage out.

                        • 0
                          Хм, странно, что на Линуксе нет. Именно на Линуксе на GCC ловил похожий баг: неинициализированная переменная типа bool, при приведении к int получалось не 0/1, а то значение байта, что лежало в bool. На Windows отрабатывало правильно.
                          • +14
                            ответ на вопрос почему — в стандарте
                            standard
                            Values of type bool are either true or false(48)
                            [Note:There are no signed,unsigned,short, or long bool types or values.— end note]
                            Values of type bool participate in integral promotions (4.5)

                            48) Using a bool value in ways described by this International Standard as “undefined,” such as by examining the value of an
                            uninitialized automatic object, might cause it to behave as if it is neither true nor false.
                            • +3

                              Хоть один комментарий по делу.

                              • 0
                                Вообще-то, нет ничего хорошего в том, что bool может иметь третье значение, не true и не false. Это противоречит основному пониманию логических переменных, общепринятому в мире (спросите у любого не знающего С++, но знакомого с логическими операциями, И, ИЛИ, сколько значений может иметь логическая переменная), а язык заявляет реализацию ООП, который исходит из объектов и свойств реальных объектов мира. И во всех книжках по С++, что я читал, (и уверен в абсолютном большинстве книг ро С++), тип bool представлен как имеющий два возможных значения.
                                Это приводит к тому, что для нормальной работы на языке мало прочесть книгу по С++, мало к тому прочесть еще и Саттера, Мейерса, но надо еще и стандарт знать. Причем, это все совершенно неочевидно после прочтения книг, которые пишутся как раз для того, чтобы можно было после них нормально писать код.
                                Ну а то, что над этим «превосходством» над обычной логикой, где у логической переменной два значения, и планирование логических операций строится в рамках этого диапазона, будут смеяться пишущие на других языках — это лишь мелкий побочный эффект.
                                • +2
                                  А много ли людей пишут код, делая switch для булевой переменной вместо одного if?
                                  if ( a ) 
                                  {
                                  }
                                  else
                                  {
                                  }
                                  • 0
                                    Правильно. Вот в этом и есть корень зла. Не надо усложнять там, где усложнять, по факту, нечего.
                                  • +2
                                    Всё верно, bool-переменная может иметь только два значения: true, false. Никакого третьего значения там быть не может. Undefined behavior, по определению, означает, что программа может вести себя как угодно, и никакую логику к ней применить уже нельзя.
                                  • +1
                                    А по моему это нормально. С++ остается высокопроизводительным языком, и нагружать его лишними проверками не стоит. Если нужны все проверки и защита от дурака, то есть более высокоуровневые языки. А тут остаются предупреждения компилятора, и если уж программист использует switch() для булевой переменной и игнорирует предупреждения — сам виноват.
                                    • +1
                                      нет ничего хорошего в том, что bool может иметь третье значение, не true и не false.

                                      Это только в случае неопределённого поведения (undefined behavior). Но в случае неопределённого поведения, по стандарту, может происходить вообще что угодно, на усмотрение компилятора. Потому оно и неопределённое. Так что здесь нечему удивляться.
                                      Это приводит к тому, что для нормальной работы на языке мало прочесть книгу по С++, мало к тому прочесть еще и Саттера, Мейерса, но надо еще и стандарт знать. Причем, это все совершенно неочевидно после прочтения книг, которые пишутся как раз для того, чтобы можно было после них нормально писать код.

                                      В любой нормальной книжке по C++ должна обязательно быть написана одна простая мысль: «Избегайте неопределённого поведения». Если в вашей книжке этого не сказано, то я даже не знаю. Наверное стоит взять другую книгу.
                                • +1
                                  В отладочной версии, свежевыделенный кусок кучи заливается неким магическим значением (0xCD), в целях отладки. Это фича Visual C++, причем отладочной конфигурации.
                                  www.microsoft.com/msj/1198/c/c1198.aspx

                                  Выделенная в стеке память в отладочной конфигурации заливается 0xCC.
                                  • –1
                                    Да, но удивление вызывает не это, а то, что MSVC генерирует код для случая "bool-значение не равно ни true, ни false" — случая, запрещённого стандартом.
                                    Для сравнения, умный компилятор вообще удаляет из этого кода ветку default, потому что знает, что стандарт гарантирует, что эта ветка не понадобится.
                                    • 0
                                      Таких значений в Visual Studio намного больше.
                                    • 0
                                      У кого есть VS, прогоните плз
                                      Заголовок спойлера
                                      int main(int argc, char** argv) {
                                      
                                          bool data;
                                          
                                          memset((void*)&data, 42, 1);
                                          
                                          switch (data)
                                          {
                                              case true:
                                              {
                                                  cout << "true";
                                                  break;
                                              }
                                              case false:
                                              {
                                                  cout << "false";
                                                  break;
                                              }
                                              default:
                                              {
                                                  cout << "default";
                                                  break;
                                              }
                                          }
                                      }



                                      Под GCC заходит в true, если последний бит — единица, иначе false.
                                      • 0
                                        Под clang так же; а под MSVC — сравнивает с единицей.
                                        (Причём и clang, и GCC удаляют обе недостижимые ветви прямо на этапе компиляции.)
                                        • 0
                                          а почему именно последний бит?
                                          • 0
                                            Стандарт гарантирует, что проверки младшего бита достаточно. (Как и сравнения с единицей достаточно.)
                                            Выбор из многих возможных способов отличить ноль от единицы — личное дело разработчиков компилятора. Наверное, на каких-то процессорах эффективнее одно, на каких-то — другое.
                                            • 0
                                              Стандарт гарантирует

                                              Сильное заявление. А пруфы можно?

                                              • 0
                                                Чуть выше в комментариях уже процитировали: «Values of type bool are either true or false»
                                                • +1

                                                  Пожалуй так, но только не потому что они "true or false", а потому что
                                                  "Values of type bool participate in integral promotions (4.5)":
                                                  4.5.6 A prvalue of type bool can be converted to a prvalue of type int, with false becoming zero and true becoming one.
                                                  Вы же о побитовых операциях говорите и сравнении с целым.

                                      • –1
                                        Второй вариант с if никогда не заходит в третий вариант.

                                        А можно перевести на русский? А то получается примерно так:

                                        Чтобы все наши устройства были в порядке,я в срочном порядке был призван к порядку, чтобы обрабатывать данные разных порядков на несколько порядков быстрее.
                                        • –2
                                          Спасибо за статью.
                                          Наконец найден ответ на извечный вопрос о качестве продукции Microsoft.
                                          Все вопросы теперь к разработчикам компилятора ;).

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