在 C++ 中,a 的位表示(甚至大小)bool
是否已定义实现;通常它被实现为char
-sized 类型,取 1 或 0 作为可能值。
如果您将其值设置为与允许的值不同的任何值(在这种特定情况下,通过别名bool
通过一个char
并修改其位表示),你就违反了语言的规则,所以任何事情都有可能发生。特别是,标准中明确规定“损坏”bool
可能表现为两者true
and false
(或两者都不true
nor false
) 同时:
Using a bool
以本国际标准描述为“未定义”的方式获取值,例如通过检查未初始化的自动对象的值,可能会导致其表现得好像两者都不是true
nor false
(C++11,[basic.fundamental],注释 47)
在这种特殊情况下,你可以看到它是如何导致这种奇怪的情况的 https://gcc.godbolt.org/z/3P5DYR: 首先if
被编译为
movzx eax, BYTE PTR [rbp-33]
test al, al
je .L22
哪个加载T
in eax
(扩展为零),如果全为零则跳过打印;下一个 if 是
movzx eax, BYTE PTR [rbp-33]
xor eax, 1
test al, al
je .L23
考试if(T == false)
变换为if(T^1)
,仅翻转低位。这对于有效的bool
,但对于你的“破损”的来说,它并没有减少它。
请注意,这个奇怪的序列仅在低优化级别下生成;在更高的级别,这通常会归结为零/非零检查,并且像您这样的序列可能会变成单个测试/条件分支 https://gcc.godbolt.org/z/9I0OR-。无论如何,在其他情况下你都会遇到奇怪的行为,例如求和时bool
值转换为其他整数:
int foo(bool b, int i) {
return i + b;
}
becomes https://gcc.godbolt.org/z/07suQv
foo(bool, int):
movzx edi, dil
lea eax, [rdi+rsi]
ret
where dil
“可信”为 0/1。
如果你的程序全部是 C++,那么解决方案很简单:不要中断bool
以这种方式值,避免弄乱它们的位表示,一切都会顺利进行;特别是,即使您将整数分配给bool
编译器将发出必要的代码以确保结果值是有效的bool
,所以你的bool T = 3
确实安全,并且T
最终会得到一个true
在它的胆量里。
相反,如果您需要与用其他语言编写的代码进行互操作,而这些语言可能不具有相同的理念bool
是,只是避免bool
对于“边界”代码,并将其编组为适当大小的整数。它适用于条件语句 & co。一样好。
有关 Fortran/互操作性方面问题的更新
免责声明我对 Fortran 的了解只是我今天早上在标准文档中读到的,而且我有一些带有 Fortran 列表的打孔卡,我将它们用作书签,所以对我要宽容一点。
首先,这种语言互操作性的东西不是语言标准的一部分,而是平台 ABI 的一部分。当我们谈论 Linux x86-64 时,相关文档是System V x86-64 ABI https://www.uclibc.org/docs/psABI-x86_64.pdf.
首先,没有任何地方指定 C_Bool
类型(定义与 C++ 相同)bool
3.1.2 注释 †) 与 Fortran 具有任何类型的兼容性LOGICAL
;特别是,在 9.2.2 中,表 9.2 规定“普通”LOGICAL
被映射到signed int
. About TYPE*N
类型它说
The “TYPE*N
” 符号指定类型的变量或聚合成员TYPE
应占据N
字节存储。
(ibid.)
没有明确指定等效类型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
为了安全起见。
However:ABI 还指定:
类型的值LOGICAL
are .TRUE.
实施为 1 和.FALSE.
实现为 0。
所以,如果你有一个程序在 a 中存储除 1 和 0 之外的任何内容LOGICAL
value, 你已经超出了 Fortran 方面的规范!你说:
一个fortranlogical*1
具有相同的表示bool
,但在 Fortran 中,如果位为 00000011,则为true
,在 C++ 中它是未定义的。
最后这句话是不正确的,Fortran 标准与表示无关,而 ABI 明确表示相反。事实上,您可以通过以下方式轻松地看到这一点:检查 gfort 的输出LOGICAL比较 https://gcc.godbolt.org/z/dh-a-c:
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
becomes
logical_compare_:
mov eax, DWORD PTR [rsi]
mov edx, 24
cmp DWORD PTR [rdi], eax
mov eax, 12
cmovne eax, edx
ret
你会注意到有一条直线cmp
介于两个值之间,无需先对它们进行标准化(与ifort
,在这方面比较保守)。
更有趣的是:无论 ABI 怎么说,ifort 默认情况下使用非标准表示LOGICAL
;这在-fpscomp logicals https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-fpscompswitch 文档,其中还指定了一些有趣的细节LOGICAL
和跨语言兼容性:
指定具有非零值的整数被视为 true,具有零值的整数被视为 false。文字常量 .TRUE。具有整数值 1 和文字常量 .FALSE。整数值为 0。此表示法由版本 8.0 之前的 Intel Fortran 版本和 Fortran PowerStation 使用。
默认为fpscomp nologicals
,它指定奇数整数值(低位 1)被视为 true,偶数整数值(低位 0)被视为 false。
文字常量 .TRUE。整数值为 -1,文字常量为 .FALSE。具有整数值 0。此表示法由 Compaq Visual Fortran 使用。 Fortran 标准未指定 LOGICAL 值的内部表示。在逻辑上下文中使用整数值的程序,或者将逻辑值传递给用其他语言编写的过程的程序是不可移植的,并且可能无法正确执行。英特尔建议您避免依赖逻辑值内部表示的编码实践。
(强调已添加)
现在,一个的内部表示LOGICAL
通常应该不成问题,因为据我所知,如果你“遵守规则”并且不跨越语言界限,你就不会注意到。对于符合标准的程序,之间没有“直接转换”INTEGER
and LOGICAL
;我认为你唯一能推开的方式INTEGER
into a LOGICAL
似乎是TRANSFER
,本质上是不可移植的并且不提供真正的保证,或者非标准INTEGER
LOGICAL
赋值时的转换。
后一种已记录 https://gcc.gnu.org/onlinedocs/gfortran/Implicitly-convert-LOGICAL-and-INTEGER-values.html通过 gfort 始终导致非零 ->.TRUE.
, 零 ->.FALSE.
, and 你可以看到 https://gcc.godbolt.org/z/y5taV2在所有情况下,都会生成代码来实现这一点(即使在使用旧表示形式的 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。
但一般来说,这些转换的情况是a bit https://www.reddit.com/r/fortran/comments/a8dzvs/ifort_implicit_logicaltointeger_conversion_of/ of a mess https://groups.google.com/forum/#!topic/gg95/yQqAlfzIBDU,所以我就远离他们。
所以,长话短说:避免投入INTEGER
数据进入LOGICAL
值,因为即使在 Fortran 中它也很糟糕,并确保使用正确的编译器标志来获取符合 ABI 的布尔值表示,并且与 C/C++ 的互操作性应该很好。但为了更加安全,我只是使用普通char
在C++方面。
最后,根据我收集到的信息从文档中 https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-scalar-types,在 ifort 中,有一些与 C 的互操作性的内置支持,包括布尔值;你可以尝试利用它。