在布尔中设置额外的位可使其同时为真和为假

BY4*_*408 40 c++ evaluation boolean abi undefined-behavior

如果我得到一个bool变量并将其第二位设置为1,则变量同时评估为true和false。使用带有-g选项(gcc-v6.3.0/Linux/RHEL6.0-2016-x86_64/bin/g++ -g main.cpp -o mytest_d)的gcc6.3编译以下代码,然后运行可执行文件。您得到以下内容。

T如何同时等于真和假?

       value   bits 
       -----   ---- 
    T:   1     0001
after bit change
    T:   3     0011
T is true
T is false
Run Code Online (Sandbox Code Playgroud)

当您使用不同的语言(例如fortran)调用函数时,可能会发生这种情况,其中对和错的定义与C ++不同。对于fortran,如果任何位都不为0,则该值为true;如果所有位为零,则该值为false。

#include <iostream>
#include <bitset>

using namespace std;

void set_bits_to_1(void* val){
  char *x = static_cast<char *>(val);

  for (int i = 0; i<2; i++ ){
    *x |= (1UL << i);
  }
}

int main(int argc,char *argv[])
{

  bool T = 3;

  cout <<"       value   bits " <<endl;
  cout <<"       -----   ---- " <<endl;
  cout <<"    T:   "<< T <<"     "<< bitset<4>(T)<<endl;

  set_bits_to_1(&T);


  bitset<4> bit_T = bitset<4>(T);
  cout <<"after bit change"<<endl;
  cout <<"    T:   "<< T <<"     "<< bit_T<<endl;

  if (T ){
    cout <<"T is true" <<endl;
  }

  if ( T == false){
    cout <<"T is false" <<endl;
  }


}
Run Code Online (Sandbox Code Playgroud)

//////////////////////////////// //使用ifort编译时,Fortran函数与C ++不兼容。

       logical*1 function return_true()
         implicit none

         return_true = 1;

       end function return_true
Run Code Online (Sandbox Code Playgroud)

Mat*_*lia 64

在C ++中,a的位表示(甚至大小)bool是实现定义的;通常将其实现为char-size类型,将1或0作为可能的值。

如果将其值设置为与允许值不同的任何值(在此特定情况下,bool通过对a 进行别名char并修改其位表示形式),将破坏该语言的规则,因此任何事情都可能发生。特别是,在标准中明确规定,“中断” bool可能同时表现为truefalse(或都不truefalse)。

bool以本国际标准描述为“未定义”的方式使用值,例如通过检查未初始化的自动对象的值,可能导致其行为既不是也不truefalse

(C ++ 11,[基本],注释47)


在这种特殊情况下,您可以看到它如何在这种奇怪的情况下结束的:第一个if被编译为

    movzx   eax, BYTE PTR [rbp-33]
    test    al, al
    je      .L22
Run Code Online (Sandbox Code Playgroud)

其负载Teax(零扩展),并跳过打印,如果这一切都为零; 下一个是

    movzx   eax, BYTE PTR [rbp-33]
    xor     eax, 1
    test    al, al
    je      .L23
Run Code Online (Sandbox Code Playgroud)

测试if(T == false)转换为if(T^1),仅翻转低位。对于有效的服务器来说bool,这是可以的,但是对于您的“破损”服务器,它并不能解决问题。

注意,这个奇怪的序列仅在低优化级别下生成;在更高级别上,这通常可以归结为零/非零检查,并且像您这样的序列很可能会成为单个测试/条件分支。在其他情况下,无论如何,您都会得到奇怪的行为,例如,将bool值求和为其他整数时:

int foo(bool b, int i) {
    return i + b;
}
Run Code Online (Sandbox Code Playgroud)

变成

foo(bool, int):
        movzx   edi, dil
        lea     eax, [rdi+rsi]
        ret
Run Code Online (Sandbox Code Playgroud)

这里dil是“信任”是0/1。


如果您的程序全部是C ++,则解决方案很简单:不要以bool这种方式破坏值,避免弄乱它们的位表示,一切都会顺利进行;特别是,即使您从整数分配给,bool编译器也会发出必要的代码,以确保结果值是有效bool,因此您bool T = 3的确是安全的,并且T最终会冒出a true

如果相反,如果您需要与用其他语言编写的可能互不相同的代码互操作bool,请避免bool使用“边界”代码,并将其编组为适当大小的整数。它将在有条件的条件下工作。一样好。


有关问题的Fortran /互操作性方面的更新

免责声明我对Fortran的了解是我今天早上在标准文档上读到的,并且我有一些带有Fortran列表的打孔卡,我将它们用作书签,所以请放轻松。

首先,这种语言互操作性不是语言标准的一部分,而是平台ABI的一部分。当我们谈论Linux x86-64时,相关文档是System V x86-64 ABI

首先,没有任何地方指定C _Bool类型(bool在3.1.2注†处定义为与C ++相同)与Fortran兼容LOGICAL。特别是在9.2.2中,表9.2指定将“普通” LOGICAL映射到signed int。关于TYPE*N类型说

TYPE*N”符号指定类型的变量或聚合成员TYPE应占用N存储字节。

(同上)

没有为显式指定等效类型LOGICAL*1,这是可以理解的:它甚至不是标准的。的确,如果您尝试编译包含LOGICAL*1符合Fortran 95兼容模式的Fortran程序,则会收到有关该警告的警告,两者均由ifort发出。

./example.f90(2): warning #6916: Fortran 95 does not allow this length specification.   [1]

    logical*1, intent(in) :: x

------------^
Run Code Online (Sandbox Code Playgroud)

并通过努力

./example.f90:2:13:
     logical*1, intent(in) :: x
             1
Error: GNU Extension: Nonstandard type declaration LOGICAL*1 at (1)
Run Code Online (Sandbox Code Playgroud)

所以水已经浑浊了;因此,结合以上两个规则,我会signed char很安全。

但是:ABI还指定:

类型的值LOGICAL.TRUE.实现为1和.FALSE. 0来实现。

因此,如果您有一个程序可以存储1和0以外的LOGICAL值,那么您在Fortran方面已经超出规格了!你说:

fortran logical*1与相同bool,但在fortran中,如果位为00000011,则为trueC,在C ++中未定义。

最后一条陈述是不正确的,Fortran标准与表示无关,而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
Run Code Online (Sandbox Code Playgroud)

变成

logical_compare_:
        mov     eax, DWORD PTR [rsi]
        mov     edx, 24
        cmp     DWORD PTR [rdi], eax
        mov     eax, 12
        cmovne  eax, edx
        ret
Run Code Online (Sandbox Code Playgroud)

您会注意到,cmp两个值之间存在一条直线,而无需先对其进行规范化(与相比ifort,这方面更保守)。

更有趣的是:无论ABI怎么说,默认情况下,ifort都使用非标准表示形式LOGICAL-fpscomp logicals交换机文档中对此进行了解释,该文档还指定了一些有趣的细节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上下文中使用整数值或将LOGICAL值传递给用其他语言编写的过程的程序是不可移植的,可能无法正确执行。英特尔建议您避免依赖于LOGICAL值的内部表示形式的编码实践。

(添加了重点)

现在,LOGICAL正常情况下的内部表示形式应该不成问题,因为据我了解,如果您“按规则”玩游戏并且不跨越语言界限,您将不会注意到。对于符合标准的程序,在INTEGER和之间没有“直接转换” LOGICAL;我认为您可以INTEGER将a 推入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
Run Code Online (Sandbox Code Playgroud)
integer_to_logical_:
        mov     eax, DWORD PTR [rdi]
        test    eax, eax
        setne   al
        ret
Run Code Online (Sandbox Code Playgroud)

a的逆向转换LOGICAL*1是一个整数零扩展(gfort),因此,为了兑现上面链接的文档中的合同,很显然,它的LOGICAL期望值为0或1。

但在一般情况下,这些转换的情况是有点一塌糊涂,所以我只希望远离他们。


因此,长话短说:避免将INTEGER数据放入LOGICAL值中,因为即使在Fortran中它也很糟糕,并确保使用正确的编译器标志来获取布尔值的ABI兼容表示形式,并且与C / C ++的互操作性应该很好。但是为了更加安全,我只char在C ++方面使用纯文本。

最后,根据我从文档中收集到的信息,在ifort中提供了对与C互操作性的内置支持,其中包括布尔值。您可以尝试利用它。

  • 实际上,bool只是一点点,它并不是在后台实现的。用来表示多少位的是实现细节(ABI的一部分),而不是语言定义的一位。我不理解为什么这是现实世界中的问题,尽管确实引起了很大的堆栈溢出问答。我在C ++和其他语言的代码之间进行了很多互操作,而我从来没有遇到过问题。@ BY408 (9认同)
  • @ BY408它看起来像您不知道如何正确地在fortran和C ++之间互操作。不要将您的无知归咎于C ++标准。标准是“精确地”存在的,因为没有它们,任何实现都将无法做任何事情,并且互操作性将是不可能的。 (7认同)

Lig*_*ica 23

当您违反了语言和编译器的合同时,就会发生这种情况。

您可能在某处听说过“零为假”和“非零为真”。当您坚持使用该语言的参数,将静态转换intbool或反之时,这一点成立。

当您开始弄乱位表示形式时,它不成立。在这种情况下,您将打破合同,并输入(至少)实现定义的行为的领域。

根本不要那样做。

如何将a bool存储在内存中不取决于您。这取决于编译器。如果要更改bool的值,请分配true/ false或分配一个整数,并使用C ++提供的适当转换机制。


C ++标准实际上用于明确指出以bool这种方式使用是顽皮的,坏的和坏的(bool以本文档描述为'undefined'的方式使用值,例如通过检查未初始化的auto对象,可能会导致它的行为好像既不是也不truefalse。”),尽管出于编辑原因在C ++ 20中将删除

  • *由编译器决定。*由C ++术语决定。在大多数平台(包括x86-64 GNU / Linux)上,编译器都遵循ABI(x86-64系统V),这是与编译器分开的文档。这不是由编译器决定的,“布尔”如何在内存中存储,这是由ABI指定的(除了私有“布尔”对象,函数外什么都看不到;然后,如果规则完全生效) )。“由编译器决定”是一个有用的简化,但这并不是真的,特别是对于GCC以外的其他编译器(因为GCC开发人员设计了x86-64 System V ABI)。 (5认同)
  • @ BY408,如果您需要一种类型来处理1和0以外的值,那么您不应该使用布尔值。使用无符号的char或int或其他数字变量。if()语句中使用的无符号char表现为您想要的方式(零为false,非零为true)。 (4认同)
  • 我对您链接的未初始化布尔UB问题的回答涵盖了所有内容,尽管:) +1不会与`bool`的对象表示混淆。 (2认同)