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 вполне работают однозначно (и будут работать так всегда на этой платформе с любым компилятором).
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.