Ответ 1
В C++ битовое представление (и даже размер) bool
определяется реализацией; обычно он реализован как тип char
-sized, принимая 1 или 0 в качестве возможных значений.
Если вы устанавливаете значение, отличное от допустимого (в данном конкретном случае путем наложения псевдонима bool
через char
и изменения его битового представления), вы нарушаете правила языка, поэтому может произойти все что угодно. В частности, в стандарте явно указано, что "неработающий" bool
может вести себя как как true
и false
(или ни true
ни false
) одновременно:
Использование значения
bool
способами, описанными в этом международном стандарте как "неопределенные", например, путем проверки значения неинициализированного автоматического объекта, может привести к тому, что он будет вести себя так, как если бы он не был ниtrue
ниfalse
(C++ 11, [basic.fundamental], примечание 47)
В этом конкретном случае вы можете увидеть, как все закончилось в этой странной ситуации: первый if
скомпилирован в
movzx eax, BYTE PTR [rbp-33]
test al, al
je .L22
который загружает T
в eax
(с нулевым расширением) и пропускает печать, если все это ноль; следующий, если вместо этого
movzx eax, BYTE PTR [rbp-33]
xor eax, 1
test al, al
je .L23
Тест if(T == false)
преобразуется в if(T^1)
, который переворачивает только младший бит. Это было бы хорошо для действительного bool
, но для вашего "сломанного" это не сокращает его.
Обратите внимание, что эта странная последовательность генерируется только при низких уровнях оптимизации; на более высоких уровнях это обычно сводится к проверке ноль/ненулевое значение, и последовательность, подобная вашей, может стать единственной проверочной/условной ветвью. В любом случае вы получите странное поведение в других контекстах, например, при суммировании значений bool
с другими целыми числами:
int foo(bool b, int i) {
return i + b;
}
foo(bool, int):
movzx edi, dil
lea eax, [rdi+rsi]
ret
где dil
является "доверенным", чтобы быть 0/1.
Если ваша программа полностью C++, то решение простое: не bool
значения bool
таким образом, избегайте путаницы с их битовым представлением, и все будет хорошо; в частности, даже если вы присваиваете целочисленное значение bool
компилятор выдаст необходимый код, чтобы убедиться, что полученное значение является допустимым bool
, поэтому ваш bool T = 3
действительно безопасен, и T
итоге получит true
в его кишках.
Если вместо этого вам нужно взаимодействовать с кодом, написанным на других языках, которые могут не иметь единого представления о том, что такое bool
, просто избегайте bool
для "граничного" кода и выставляйте его как подходящее целое число -sized. Это будет работать в условных условиях. так же хорошо.
Обновление о Fortran/совместимости стороны вопроса
Отказ от ответственности Все, что я знаю о Фортране, это то, что я читаю сегодня утром на стандартных документах, и что у меня есть несколько перфокарт с листингами на Фортране, которые я использую в качестве закладок, так что будьте спокойны со мной.
Прежде всего, этот тип взаимодействия языков не является частью языковых стандартов, а является платформой ABI. Поскольку мы говорим о Linux x86-64, соответствующим документом является System V x86-64 ABI.
Прежде всего, нигде не указано, что тип C _Bool
(который определен как C++ bool
в примечании 3.1.2) имеет какую-либо совместимость с Fortran LOGICAL
; в частности, в 9.2.2 таблица 9.2 указывает, что "обычный" LOGICAL
сопоставлен со signed int
. О типах TYPE*N
говорится, что
Нотация "
TYPE*N
" указывает, что переменные или агрегированные члены типаTYPE
должны заниматьN
байтов памяти.
(там же.)
Там нет эквивалентного типа явно указан для LOGICAL*1
, и это понятно: это даже не стандарт; действительно, если вы попытаетесь скомпилировать программу на Fortran, содержащую LOGICAL*1
в режиме совместимости с Fortran 95, вы получите предупреждения об этом, оба от ifort
./example.f90(2): warning #6916: Fortran 95 does not allow this length specification. [1]
logical*1, intent(in) :: x
------------^
и по комфорту
./example.f90:2:13:
logical*1, intent(in) :: x
1
Error: GNU Extension: Nonstandard type declaration LOGICAL*1 at (1)
поэтому воды уже запутаны; так что, сочетая два правила выше, я бы пошел на signed char
чтобы быть в безопасности.
Тем не менее: ABI также указывает:
Значения для типа
LOGICAL
:.TRUE.
реализовано как 1 и.FALSE.
реализовано как 0.
Итак, если у вас есть программа, которая хранит что-то кроме 1 и 0 в LOGICAL
значении, вы уже не в спецификации на стороне Фортрана! Ты говоришь:
Фортран
logical*1
имеет то же представление, что иbool
, но в фортране, если биты 00000011, этоtrue
, в C++ он не определен.
Последнее утверждение неверно, стандарт Фортрана не зависит от представления, а ABI прямо говорит об обратном. Действительно, вы можете легко убедиться в этом, проверив вывод gfort для LOGICAL
сравнения:
integer function logical_compare(x, y)
logical, intent(in) :: x
logical, intent(in) :: y
if (x .eqv. y) then
logical_compare = 12
else
logical_compare = 24
end if
end function logical_compare
становится
logical_compare_:
mov eax, DWORD PTR [rsi]
mov edx, 24
cmp DWORD PTR [rdi], eax
mov eax, 12
cmovne eax, edx
ret
Вы заметите, что между ifort
двумя значениями есть прямая cmp
, без предварительной нормализации их (в отличие от ifort
, это более консервативно в этом отношении).
Еще интереснее: независимо от того, что говорит ABI, ifort по умолчанию использует нестандартное представление для LOGICAL
; это объясняется в документации по переключателю -fpscomp logicals
, в которой также указаны некоторые интересные детали о LOGICAL
и кросс-языковой совместимости:
Указывает, что целые числа с ненулевым значением обрабатываются как true, целые числа с нулевым значением рассматриваются как false. Буквальная константа. TRUE. имеет целочисленное значение 1 и литеральную константу .FALSE. имеет целочисленное значение 0. Это представление используется в выпусках Intel Fortran до версии 8.0 и Fortran PowerStation.
По умолчанию используется
fpscomp nologicals
, который указывает, что нечетные целочисленные значения (fpscomp nologicals
бит один) обрабатываются как истина, а четные целочисленные значения (младший бит ноль) рассматриваются как ложь.Буквальная константа. TRUE. имеет целочисленное значение -1 и литеральную константу .FALSE. имеет целочисленное значение 0. Это представление используется Compaq Visual Fortran. Внутреннее представление значений LOGICAL не указано стандартом Fortran. Программы, которые используют целочисленные значения в контекстах LOGICAL или передают значения LOGICAL процедурам, написанным на других языках, являются непереносимыми и могут работать некорректно. Intel рекомендует избегать практики кодирования, которая зависит от внутреннего представления значений LOGICAL.
(выделение добавлено)
Теперь внутреннее представление LOGICAL
обычно не должно быть проблемой, поскольку, насколько я понимаю, если вы играете "по правилам" и не пересекаете языковые границы, вы не заметите этого. Для стандартной совместимой программы не существует "прямого преобразования" между INTEGER
и LOGICAL
; Единственный способ, которым я вижу, что вы можете вставить INTEGER
в LOGICAL
- это TRANSFER
, который по сути непереносим и не дает никаких реальных гарантий, или нестандартное преобразование INTEGER
<-> LOGICAL
при назначении.
Последнее задокументировано gfort, чтобы всегда приводить к ненулевому значению → .TRUE.
, ноль ->. .FALSE.
и вы можете видеть, что во всех случаях генерируется код, чтобы это произошло (даже если он запутывает код в случае ifort с унаследованным представлением), поэтому вы не можете вставить произвольное целое число в LOGICAL
таким образом.
logical*1 function integer_to_logical(x)
integer, intent(in) :: x
integer_to_logical = x
return
end function integer_to_logical
integer_to_logical_:
mov eax, DWORD PTR [rdi]
test eax, eax
setne al
ret
Обратное преобразование для LOGICAL*1
является прямым целочисленным нулевым расширением (gfort), поэтому для соблюдения контракта в документации, указанной выше, очевидно, что значение LOGICAL
будет равно 0 или 1.
Но в целом ситуация с этими конверсиями немного беспорядочная, поэтому я бы просто держался от них подальше.
Итак, короткая история: избегайте помещения данных INTEGER
в значения LOGICAL
, поскольку это плохо даже в Fortran, и убедитесь, что вы используете правильный флаг компилятора, чтобы получить ABI-совместимое представление для логических значений, и взаимодействие с C/C++ должно быть в порядке. Но для большей безопасности я бы просто использовал обычный char
на стороне C++.
Наконец, из того, что я извлекаю из документации, в ifort есть некоторая встроенная поддержка взаимодействия с C, включая логические значения; Вы можете попытаться использовать это.