簡體   English   中英

在bool中設置額外位會使其同時為true和false

[英]Setting extra bits in a bool makes it true and false at the same time

如果我得到一個bool變量並將其第二位設置為1,則變量同時計算為true和false。 使用帶-g選項的gcc6.3編譯以下代碼( gcc-v6.3.0/Linux/RHEL6.0-2016-x86_64/bin/g++ -g main.cpp -o mytest_d )並運行可執行文件。 你得到以下。

T如何同時等於真和假?

       value   bits 
       -----   ---- 
    T:   1     0001
after bit change
    T:   3     0011
T is true
T is false

當您使用不同語言(例如fortran)調用函數時,可能會發生這種情況,其中true和false定義與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;
  }


}

/////////////////////////////////// //使用ifort編譯時與C ++不兼容的Fortran函數。

       logical*1 function return_true()
         implicit none

         return_true = 1;

       end function return_true

在C ++中, bool的位表示(甚至大小)是實現定義的; 通常它被實現為char -sized類型,取1或0作為可能的值。

如果將其值設置為與允許值不同的任何值(在此特定情況下,通過charbool別名化並修改其位表示),則會破壞語言規則,因此任何事情都可能發生。 特別是,在標准中明確規定,“破壞”的bool可能同時表現為truefalse (或既不是true也不是false ):

以本國際標准描述的方式將bool值用作“未定義”,例如通過檢查未初始化的自動對象的值,可能會使其表現為既不是true也不是false

(C ++ 11,[basic.fundamental],注47)


在這種特殊情況下, 你可以看到它在這種奇怪的情況下是如何結束的 :第一個if被編譯到

    movzx   eax, BYTE PTR [rbp-33]
    test    al, al
    je      .L22

它在eax中加載T (零擴展),如果全部為零,則跳過打印; 相反,下一個是

    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 T = 3確實是安全的,而T最終會得到一個true在它的膽量。

相反,如果你需要與其他語言編寫的代碼進行互操作,這些代碼可能不同於bool的相同概念,只需避免bool代表“邊界”代碼,並將其編組為適當大小的整數。 它將在條件和合作。 同樣好。


有關該問題的Fortran /互操作性方面的更新

免責聲明我所知道的Fortran就是我今天早上在標准文檔上看到的內容,而且我有一些用Fortran列表打孔的卡片,我用作書簽,所以請放輕松。

首先,這種語言互操作性的東西不是語言標准的一部分,而是ABI平台的一部分。 在我們討論Linux x86-64時,相關文檔是System V x86-64 ABI

首先,沒有指定C _Bool類型(在3.1.2注意†中定義為與C ++ bool相同)與Fortran LOGICAL有任何兼容性; 特別是在9.2.2表9.2中指定將“plain” LOGICAL映射到signed int 關於TYPE*N類型,它說

了“ TYPE*N N ”表示法指定了變量或類型的骨料成員TYPE應占據N存儲的字節。

(同上)

沒有為LOGICAL*1明確指定的等效類型,這是可以理解的:它甚至不是標准的; 事實上,如果您嘗試在Fortran 95兼容模式下編譯包含LOGICAL*1的Fortran程序,您會收到有關它的警告

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

    logical*1, intent(in) :: x

------------^

並且由gfort

./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。

所以,如果你有一個程序在LOGICAL值中存儲除1和0之外的任何東西, 那么你已經超出了Fortran方面的規范 你說:

fortran logical*1具有與bool相同的表示,但是如果位是00000011則在fortran中是true ,在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

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交換機文檔中進行了解釋,該文檔還指定了有關LOGICAL和跨語言兼容性的一些有趣細節:

指定具有非零值的整數被視為true,具有零值的整數被視為false。 文字常量.TRUE。 整數值為1,文字常量為FALSE。 整數值為0.此表示形式由版本8.0之前的英特爾Fortran版本和Fortran PowerStation使用。

默認值為fpscomp nologicals ,它指定奇數值(低位1)被視為true,偶數整數值(低位0)被視為false。

文字常量.TRUE。 整數值為-1,文字常量為.FALSE。 整數值為0. Compaq Visual Fortran使用此表示形式。 Fortran標准未指定LOGICAL值的內部表示。 在LOGICAL上下文中使用整數值或將LOGICAL值傳遞給用其他語言編寫的過程的程序是不可移植的,可能無法正確執行。 英特爾建議您避免使用依賴於LOGICAL值內部表示的編碼實踐。

(重點補充)

現在, LOGICAL的內部表示通常不應該成為問題,因為從我收集的內容來看,如果你按照規則進行游戲並且不跨越語言邊界,你就不會注意到。 對於符合標准的程序, INTEGERLOGICAL之間沒有“直接轉換”; 我認為你可以將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 ++的互操作性應該沒問題。 但為了更安全,我只是在C ++方面使用plain char

最后,根據我從文檔中收集的內容 ,在ifort中有一些內置支持與C的互操作性,包括布爾值; 你可以嘗試利用它。

當您違反與語言和編譯器的合同時會發生這種情況。

你可能聽說過“零是假的”,“非零是真的”。 當你堅持語言的參數,靜態地將int轉換為bool或反之亦然時,這就成立了。

當你開始搞亂位表示時它不成立。 在這種情況下,您違反合同,並進入(至少)實現定義的行為領域。

根本不要那樣做。

這不取決於bool如何存儲在內存中。 這取決於編譯器。 如果要更改bool的值,請指定true / false ,或者指定一個整數並使用C ++提供的正確轉換機制。


C ++標准實際上給出了一個特定的調用,告訴我們如何以這種方式使用bool是頑皮的,壞的和邪惡的( “使用bool值以本文檔描述的方式為'undefined',例如通過檢查一個值未初始化的自動對象,可能會使它表現得好像既不是true也不是false 。“ ),盡管出於編輯原因它已在C ++ 20中刪除

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM