[英]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作為可能的值。
如果將其值設置為與允許值不同的任何值(在此特定情況下,通過char
將bool
別名化並修改其位表示),則會破壞語言規則,因此任何事情都可能發生。 特別是,在標准中明確規定,“破壞”的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
它在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列表打孔的卡片,我用作書簽,所以請放輕松。
首先,這種語言互操作性的東西不是語言標准的一部分,而是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
的內部表示通常不應該成為問題,因為從我收集的內容來看,如果你按照規則進行游戲並且不跨越語言邊界,你就不會注意到。 對於符合標准的程序, 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 ++的互操作性應該沒問題。 但為了更安全,我只是在C ++方面使用plain char
。
當您違反與語言和編譯器的合同時會發生這種情況。
你可能聽說過“零是假的”,“非零是真的”。 當你堅持語言的參數,靜態地將int
轉換為bool
或反之亦然時,這就成立了。
當你開始搞亂位表示時它不成立。 在這種情況下,您違反合同,並進入(至少)實現定義的行為領域。
根本不要那樣做。
這不取決於bool
如何存儲在內存中。 這取決於編譯器。 如果要更改bool
的值,請指定true
/ false
,或者指定一個整數並使用C ++提供的正確轉換機制。
C ++標准實際上給出了一個特定的調用,告訴我們如何以這種方式使用bool
是頑皮的,壞的和邪惡的( “使用bool
值以本文檔描述的方式為'undefined',例如通過檢查一個值未初始化的自動對象,可能會使它表現得好像既不是true
也不是false
。“ ),盡管出於編輯原因它已在C ++ 20中被刪除 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.