C++/C bool != true и false
От: Jukier  
Дата: 26.06.14 18:37
Оценка: -1 :)
Есть способ получить переменную bool не равную ни true, ни false. При этом разные компиляторы работают по-разному. В Microsoft 2005 в следующем фрагменте кода можно сравнивать переменную b c true или false, и мы зайдем в else (если мне не изменяет память). В gcc (точно в последних версиях) уже необходимо сравнивать с функцией bool g(), чтобы зайти в else.

Есть два вопроса:
1. Какое поведение должно быть по стандарту, может ли переменная типа bool быть не равна ни true, ни false?
2. Какое поведение в других компиляторах/версиях?

#include <stdio.h>

int f()
{
    return 4;
}

bool g()
{
    return true;
}

int main() {

    //bool b = reinterpret_cast<bool (*)()>(f)();
    bool b = ((bool (*)())f)();

    if (b == g()) // b == true
    {
        printf("true");
    }
    else if (b == !g()) // b == false
    {
        printf("false");
    }
    else
    {
        printf("bool is not equal to true or false");
    }

    return 0;
}


P.S. Аналогичная ситуация с языком C и типом _Bool, не равным ни 1, ни 0.
Re: C++/C bool != true и false
От: watchmaker  
Дата: 26.06.14 19:12
Оценка: 11 (2) +1
Здравствуйте, Jukier, Вы писали:

J>Есть два вопроса:

J>1. Какое поведение должно быть по стандарту,
По стандарту тут UB:

A function pointer can be explicitly converted to a function pointer of a different type. The effect of calling
a function through a pointer to a function type (8.3.5) that is not the same as the type used in the definition
of the function is undefined.

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

J> может ли переменная типа bool быть не равна ни true, ни false?

В правильной программе на С++ — не может.


J>2. Какое поведение в других компиляторах/версиях?


Всё то же UB.
Re: C++/C bool != true и false
От: _niko_ Россия  
Дата: 26.06.14 19:12
Оценка:
Здравствуйте, Jukier, Вы писали:

J>может ли переменная типа bool быть не равна ни true, ни false?

Конечно, ведь ты же сам "сказал" компилятору (reinterpret_cast) что то что записано в int это не int вовсе а bool, он же согласно букве закона стандарта тебе не перечит — верит на слово.

J>Какое поведение должно быть по стандарту

В общем случае UB.
На деле если тот тип что на месте int'а "влазит" в тот тип что на месте bool'а — может и обойдется
Re[2]: C++/C bool != true и false
От: Jukier  
Дата: 27.06.14 04:21
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Здравствуйте, Jukier, Вы писали:


J>>Есть два вопроса:

J>>1. Какое поведение должно быть по стандарту,
W>По стандарту тут UB:

A function pointer can be explicitly converted to a function pointer of a different type. The effect of calling
W>a function through a pointer to a function type (8.3.5) that is not the same as the type used in the definition
W>of the function is undefined.


Спасибо за указание места, где это определено.

W>Я понимаю, что сама задача определить как компилятор представляет тип bool на низком уровне может быть занятной. Но ты делаешь это неправильно, а твой код ломается раньше.


В Microsoft VS 2005 bool занимает 1 байт: true == 1, false == 0. А в этом примере b == 4 (и там, как я помню, для захода в else функция g() не была нужна). Меня удивило, что в gcc (g++) есть разница между сравнением с true и false напрямую и через функцию.

J>>2. Какое поведение в других компиляторах/версиях?


W>Всё то же UB.


Если есть под рукой последняя Microsoft Visual Studio (или более поздняя, чем 2005), то буду благодарен, если посмотрите, как работает там приведенный кусок кода с явным сравнением с true и false.
Re[2]: C++/C bool != true и false
От: Jukier  
Дата: 27.06.14 04:28
Оценка:
Здравствуйте, _niko_, Вы писали:

__>Здравствуйте, Jukier, Вы писали:


J>>может ли переменная типа bool быть не равна ни true, ни false?

__>Конечно, ведь ты же сам "сказал" компилятору (reinterpret_cast) что то что записано в int это не int вовсе а bool, он же согласно букве закона стандарта тебе не перечит — верит на слово.

Этот момент я понял, но в стандарте есть про приведение других типов к bool, где сказано, что все ненулевое — это true.

J>>Какое поведение должно быть по стандарту

__>В общем случае UB.
__>На деле если тот тип что на месте int'а "влазит" в тот тип что на месте bool'а — может и обойдется

Спасибо за ответ, но если "влазит", то тоже не обойдется, проверено (см. ответ на пред. пост).
Re[3]: C++/C bool != true и false
От: _niko_ Россия  
Дата: 27.06.14 06:07
Оценка:
Здравствуйте, Jukier, Вы писали:

J>... в стандарте есть про приведение других типов к bool, где сказано, что все ненулевое — это true.

это про static_cast<bool>(...) — если с ним скомпилилось значит привидение типа пойдет по стандарту.
reinterpret_cast никакое привидение не поддерживает — как я уже писал тут компилятор перестает брыкаться и делает как велино.
Re[3]: C++/C bool != true и false
От: watchmaker  
Дата: 01.07.14 16:29
Оценка: 25 (2)
Здравствуйте, Jukier, Вы писали:

J> Меня удивило, что в gcc (g++) есть разница между сравнением с true и false напрямую и через функцию.


Я боюсь, что у тебя уже появляются не совсем верные ассоциации между представлением bool и «сравнением напрямую и через функцию». То есть связь там действительно может быть (об этом ниже), но формально в твоём примере до этого не доходит. У тебя там UB при вызове функций (а не при сравнении bool) — и на этом можно заканчивать разговор, код может делать что угодно и это уже не обязательно связано с самим bool. Вот, например, один из компиляторов решает что будет выводить ещё до запуска программы: http://goo.gl/Hr9xWL — то есть там сравнений вообще не делается, а вся программа просто вырождается в единственный вызов печати. Чем это вырождение объясняется? Твой экспериментальный код не даёт ответа.

J>В Microsoft VS 2005 bool занимает 1 байт: true == 1, false == 0. А в этом примере b == 4 (и там, как я помню, для захода в else функция g() не была нужна).


Короче, тут всё не так просто. То что в памяти bool занимает один байт (и имеет два разрешенных значения 0 и 1) совсем не означает, что в регистре он будет представлен таким же образом, и тем более это не означает, что при передачи в другие процедуры (написанные в том числе на других языках) будет сохраняться то же соглашение. В общем случае не верно, что во всех трёх вышеописанных ситуациях bool на низком уровне хранится и трактуется одинаковым образом.
И чтобы в этом всём не запутаться придумали документацию — ABI. И лучше документацию читать

Вот, например, популярная платформа system V amd64 abi. Если открыть этот документ, то можно прочитать, что в памяти bool всегда занимает 1 байт и всегда равен 0 (ложь) или 1 (истина). Но тот же bool в регистре ведёт себя по другому — истиной считается уже не только 1, но и вообще любые ненулевые значения. При вызове же процедур значение bool хранится в младшем бите, при этом остальные биты младшего байта нулевые, а остальные биты — не определены.

Смог бы ты без документации своими экспериментами эти правила вывести? Вряд-ли. Да и бессмысленное это занятие. Прочитать — и проще, и надёжнее.
Начинать нужно с учебников и справочников, а не бросаться сразу писать глючный код и пытаться его объяснить.

Впрочем, после прочтения документации можно уже и проверять полученные знания
# nasm -f elf64 -o fn.o fn.asm
global foo, bar, baz

foo:
    mov rax, 18364758544493064704
    ret

bar:
    mov rax, 18437098717332255489
    ret

baz:
    mov rax, 3
    ret
Так согласно ABI функция foo возвращает значение false, функция bar — значение true, а функция baz — запрещённое значение.
То есть любой компилятор C/C++ на упомянутой платформе будет корректно (и полностью определённо) работать с результатом вызова foo или bar, но поведение при вызове baz неопределено.
Небольшая проверка:
#include <cstdio>
extern "C" bool foo();
extern "C" bool bar();
extern "C" bool baz();

void test(const char* name, bool (*fn)()) {
    printf("%s:\n", name);
    const char* str[2] = {"\tfalse", "\ttrue"};
    switch(fn()) {
        case false: puts("\tfalse"); break;
        case true: puts("\ttrue"); break;
        default: puts("\t?"); break;
    }
    puts(str[fn()]);
}

int main() {
#define TEST(fn) test(#fn, fn)
    TEST(foo);
    TEST(bar);
    TEST(baz);
}

И вот такие возможные выводы наблюдаются при разных компиляторах и разных их настройках:
  gcc 4.8.2 -O1
foo:
    false
    false
bar:
    true
    true
baz:
    ?

  gcc 4.8.2 -O2
foo:
    false
    false
bar:
    true
    true
baz:
    false
1�H��1�I��^H��H���PTI���@

  clang 3.4-1 -O2
foo:
    false
    false
bar:
    true
    true
baz:
    true
Segmentation fault (core dumped)


Видно, что с baz вечно происходит что-то странное. А вот foo и bar вполне работают однозначно (и будут работать так всегда на этой платформе с любым компилятором).
Re[4]: C++/C bool != true и false
От: Jukier  
Дата: 03.07.14 18:08
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Короче, тут всё не так просто...


Благодарен за столь полный ответ, с огромным удовольствием прочитал (все бы так отвечали).

W>Я боюсь, что у тебя уже появляются не совсем верные ассоциации между представлением bool и «сравнением напрямую и через функцию». То есть связь там действительно может быть (об этом ниже), но формально в твоём примере до этого не доходит. У тебя там UB при вызове функций (а не при сравнении bool) — и на этом можно заканчивать разговор, код может делать что угодно и это уже не обязательно связано с самим bool.


У меня вопросы возникают: как в каком компиляторе сделано и почему. То что UB, а дальше можно форматировать диск, меня не удовлетворяет . Ну дальше сам почитаю, ещё раз спасибо.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.