web-dev-qa-db-ja.com

ブールに追加のビットを設定すると、ブール値がtrueとfalseに同時になります

bool変数を取得し、その2番目のビットを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を同時にtrueとfalseに等しくするにはどうすればよいですか?

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

これは、trueおよびfalseの定義がC++とは異なる別の言語(fortranなど)で関数を呼び出すときに発生する可能性があります。 Fortranでは、いずれかのビットが0でない場合、値はtrueです。すべてのビットが0の場合、値は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
41
BY408

C++では、boolのビット表現(およびサイズ)は実装定義です。一般に、1または0を可能な値として取るchar- size型として実装されます。

その値を許可されたものとは異なる値に設定すると(この特定のケースでは、boolcharを介してエイリアスを設定し、そのビット表現を変更することにより)、言語の規則に違反しているため、何かが起こる可能性があります。特に、「壊れた」boolは、truefalseの両方として(またはtruefalseの両方として)同時に動作する可能性があることが、標準で明示的に指定されています。

初期化されていない自動オブジェクトの値を調べるなど、この国際標準で「未定義」として記述されている方法でbool値を使用すると、trueでもfalseでもないかのように動作する場合があります。

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


この特定のケースでは、 この奇妙な状況でどのように終わったかを見ることができます :最初のifがコンパイルされます

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

Teaxにロードし(拡張子はゼロ)、すべてゼロの場合は印刷をスキップします。代わりに次の場合

_    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_は確かに安全であり、Ttrueは根性に欠けています。

代わりに、boolと同じ概念を共有しない他の言語で記述されたコードと相互運用する必要がある場合は、「境界」コードのboolを避け、適切なサイズの整数としてマーシャリングしてください。それは条件付き&coで動作します。まったく同じです。


問題の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は、「プレーン」LOGICALが_signed int_にマップされることを指定しています。 _TYPE*N_タイプについて

「_TYPE*N_」表記は、TYPE型の変数または集約メンバーがNバイトのストレージを占有することを指定します。

(同上)

_LOGICAL*1_に明示的に指定された同等のタイプはなく、それは理解できます。それは標準でさえありません。実際、_LOGICAL*1_を含むFortranプログラムを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)
_

そのため、水はすでに混濁しています。したがって、上記の2つのルールを組み合わせて、安全のために_signed char_を使用します。

ただし:ABIは以下も指定します:

タイプLOGICALの値は、1として実装された_.TRUE._および0として実装された_.FALSE._です。

したがって、LOGICAL値に1と0以外のものを格納するプログラムがある場合、Fortran側の仕様はすでに外れています!あなたは言う:

Fortran _logical*1_の表記はboolと同じですが、fortranではビットが00000011の場合はtrueであり、C++では未定義です。

この最後のステートメントは真実ではありません。Fortran標準は表現にとらわれず、ABIは反対を明確に述べています。実際、これは実際に LOGICAL比較のためにgfortの出力をチェックする によって簡単に確認できます。

_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
_

最初に正規化せずに、2つの値の間にまっすぐな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で使用されます。 LOGICAL値の内部表現は、Fortran規格では指定されていません。 LOGICALコンテキストで整数値を使用するプログラム、または他の言語で記述されたプロシージャにLOGICAL値を渡すプログラムは、移植性がなく、正しく実行されない可能性があります。インテルは、LOGICAL値の内部表現に依存するコーディング方法を避けることをお勧めします。

(強調を追加)

ここで、LOGICALの内部表現は通常問題になりません。私が収集したものから、 "ルールに従って"プレイし、言語の境界を越えない場合は、気づかないでしょう。標準に準拠したプログラムの場合、INTEGERLOGICALの間に「直接変換」はありません。 INTEGERLOGICALに押し込める唯一の方法は、TRANSFERです。これは本質的に移植不可能であり、実際の保証はありません。または、割り当て時の非標準のINTEGER <-> LOGICAL変換です。

後者は 文書化されています 常にゼロ以外の結果になるように努力します-> _.TRUE._、ゼロ-> _.FALSE._、そして あなたは見ることができます これを実現するためにコードが生成されます(レガシー表現を使用した場合の複雑なコードですが)。この方法で任意の整数を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++側では単純なcharを使用します。

最後に、私が収集したもの ドキュメントから には、ブール値を含め、Cとの相互運用性の組み込みサポートがあります。あなたはそれを活用しようとするかもしれません。

65
Matteo Italia

言語とコンパイラの両方との契約に違反すると、これが起こります。

「ゼロは偽である」、「ゼロ以外は真である」とどこかで聞いたことがあるでしょう。言語のパラメーターにこだわって、intboolに静的に、またはその逆に静的に変換する場合も同様です。

あなたがビット表現をいじり始めるときそれは保持されません。その場合、契約を破って、(少なくとも)実装定義の動作の領域に入ります。

単にそれをしないでください。

boolをメモリに格納する方法はあなた次第ではありません。それはコンパイラ次第です。 boolの値を変更する場合は、true/falseを割り当てるか、整数を割り当てて、C++が提供する適切な変換メカニズムを使用します。


この方法でboolを使用する方法に実際に特定のコールアウトを与えるために使用されるC++標準は、いたずらで悪、悪( "bool値の使用初期化されていない自動オブジェクトの値を調べるなど、このドキュメントで「未定義」と説明されている方法では、trueでもfalseでもないかのように動作する可能性があります。 ")、ただし 編集上の理由によりC++ 20で削除されました