Веселье с неинициализированными переменными и компилятором (GCC) - PullRequest
17 голосов
/ 02 февраля 2011

Раздел §3.9.1 / 6 стандарта C ++ гласит:

Значения типа bool: true или false.

Сейчасрассмотрим этот код,

void f(bool b)
{
    switch(b) //since b is bool, it's value can be either true or false!
    {
        case true: cout << "possible value - true";  break;
        case false: cout << "possible value - false"; break;
        default: cout << "impossible value";
    }
}
int main()
{
    bool b; //note : b is uninitialized
    f(b);
    return 0;
}

Компиляция F:\workplace>g++ test.cpp -pedantic

Выполнение.Вывод:

невозможное значение

Неожиданный вывод?Ну, не совсем, поскольку Стандарт гласит в сноске § 3.9.1 / 6, что:

Использование значения bool способами, описанными в этом Международном стандарте как «undefined» , например , проверяя значение неинициализированного автоматического объекта, может заставить его вести себя так, как если бы оно не было ни истинным, ни ложным .

Такнезависимо от того, сколько раз я компилирую и запускаю эту программу, я получаю один и тот же вывод: impossible value.Однако, если я немного ее изменю - удаляю функцию f() с картинки и записываю блок switch в main():

int main()
{
    bool b; //note : b is uninitialized
    switch(b) //since b is bool, it's value can be either true or false!
    {
        case true: cout << "possible value - true";  break;
        case false: cout << "possible value - false"; break;
        default: cout << "impossible value";
    }
    return 0;
}

Затем я компилирую и запускаю эту программу, яне получайте impossible value в качестве вывода;независимо от того, сколько раз я повторяю это, я никогда не получаю impossible value.

Мне просто любопытно узнать, почему это внезапное изменение в поведении неинициализированного bool?

Что ж, с точки зрения языка ясно: поведение не определено. Я понимаю это.Я также понимаю, что компилятор может делать все что угодно.Однако с точки зрения компилятора это кажется мне очень интересным.Что мог бы сделать компилятор (т.е. GCC) в каждом случае и почему?

Я использую: g++ (GCC) 4.5.0 - MinGW, on Windows 7 Basic, 64-bit OS.

Ответы [ 2 ]

17 голосов
/ 02 февраля 2011

Мне просто интересно узнать, почему это внезапное изменение в поведении неинициализированного bool?

Разберите код и посмотрите, что делает компилятор.

Мое предположение: поскольку значение теперь используется только локально, компилятор полностью его оптимизирует. Так как поведение в любом случае не определено, компилятор может просто принять любое значение, например false. Это довольно очевидная оптимизация, поскольку значение b является постоянным для компилятора, а вся логика switch является избыточной. Так зачем помещать его в исполняемый файл?

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

1 голос
/ 24 апреля 2018

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

У меня был какой-то код, который сводился к

if(!(a == b && c.d())) { do_something(); }

Ошибка, которую я преследовал, заключалась в том, что do_something() происходило неправильно. И все же a определенно равнялось b, а c.d(), казалось, возвращало истину.

Поскольку я отслеживал это, я временно добавил эти тестовые распечатки:

if(  a == b && c.d() ) printf("yes\n"; else printf("no\n");
if(!(a == b && c.d())) printf("noo\n"; else printf("yess\n");

К моему удивлению, это напечатанные yes и noo, которые подтверждали и то, почему do_something происходило, и что происходило что-то очень странное.

Оказалось, что метод d() был чем-то вроде

bool whatever::d() {
    return _successful;
}

Но _successful был неинициализирован. Когда я распечатал его значение, оно было 236, поэтому ранее я говорил, что «c.d(), похоже, возвращает истину».

Я не проверял ассемблерный код, но я предполагаю, что в некоторых обстоятельствах gcc проверял, был ли он ненулевым или нет, но в других случаях он просто проверял младший бит.

При правильной инициализации _successful ошибка исчезла. (Он был неинициализирован в течение десять лет , так как более ранний программист впервые написал метод d(). Однако ошибка не проявлялась всего несколько месяцев назад. Вот почему иногда Software Is Hard.)

...