Установка дополнительных битов в bool делает его истинным и ложным одновременно

Если я получу переменную bool и установлю ее второй бит на 1, тогда переменная будет иметь значение true и false одновременно. Скомпилируйте следующий код с gcc6.3 с параметром -g (gcc-v6.3.0/Linux/RHEL6.0-2016-x86_64/bin/g++ -g main.cpp -o mytest_d) и запустите исполняемый файл. Вы получаете следующее.

Как T может быть равным true и false одновременно?

       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;
  }


}

/////////////////////////////////////Функция Фортрана, которая не совместима с C++ при компиляции с ifort.

       logical*1 function return_true()
         implicit none

         return_true = 1;

       end function return_true

Ответы

Ответ 1

В C++ битовое представление (и даже размер) bool определяется реализацией; обычно он реализован как тип char -sized, принимая 1 или 0 в качестве возможных значений.

Если вы устанавливаете значение, отличное от допустимого (в данном конкретном случае путем наложения псевдонима bool через char и изменения его битового представления), вы нарушаете правила языка, поэтому может произойти все что угодно. В частности, в стандарте явно указано, что "неработающий" 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

который загружает T в eax (с нулевым расширением) и пропускает печать, если все это ноль; следующий, если вместо этого

    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, поэтому ваш bool T = 3 действительно безопасен, и T итоге получит true в его кишках.

Если вместо этого вам нужно взаимодействовать с кодом, написанным на других языках, которые могут не иметь единого представления о том, что такое bool, просто избегайте bool для "граничного" кода и выставляйте его как подходящее целое число -sized. Это будет работать в условных условиях. так же хорошо.


Обновление о Fortran/совместимости стороны вопроса

Отказ от ответственности Все, что я знаю о Фортране, это то, что я читаю сегодня утром на стандартных документах, и что у меня есть несколько перфокарт с листингами на Фортране, которые я использую в качестве закладок, так что будьте спокойны со мной.

Прежде всего, этот тип взаимодействия языков не является частью языковых стандартов, а является платформой ABI. Поскольку мы говорим о Linux x86-64, соответствующим документом является System V x86-64 ABI.

Прежде всего, нигде не указано, что тип C _Bool (который определен как C++ bool в примечании 3.1.2) имеет какую-либо совместимость с Fortran LOGICAL; в частности, в 9.2.2 таблица 9.2 указывает, что "обычный" LOGICAL сопоставлен со signed int. О типах TYPE*N говорится, что

Нотация " TYPE*N " указывает, что переменные или агрегированные члены типа TYPE должны занимать N байтов памяти.

(там же.)

Там нет эквивалентного типа явно указан для 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 чтобы быть в безопасности.

Тем не менее: ABI также указывает:

Значения для типа LOGICAL: .TRUE. реализовано как 1 и .FALSE. реализовано как 0.

Итак, если у вас есть программа, которая хранит что-то кроме 1 и 0 в LOGICAL значении, вы уже не в спецификации на стороне Фортрана! Ты говоришь:

Фортран logical*1 имеет то же представление, что и bool, но в фортране, если биты 00000011, это true, в C++ он не определен.

Последнее утверждение неверно, стандарт Фортрана не зависит от представления, а 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

Вы заметите, что между ifort двумя значениями есть прямая cmp, без предварительной нормализации их (в отличие от ifort, это более консервативно в этом отношении).

Еще интереснее: независимо от того, что говорит ABI, ifort по умолчанию использует нестандартное представление для LOGICAL; это объясняется в документации по переключателю -fpscomp logicals, в которой также указаны некоторые интересные детали о LOGICAL и кросс-языковой совместимости:

Указывает, что целые числа с ненулевым значением обрабатываются как true, целые числа с нулевым значением рассматриваются как false. Буквальная константа. TRUE. имеет целочисленное значение 1 и литеральную константу .FALSE. имеет целочисленное значение 0. Это представление используется в выпусках Intel Fortran до версии 8.0 и Fortran PowerStation.

По умолчанию используется fpscomp nologicals, который указывает, что нечетные целочисленные значения (fpscomp nologicals бит один) обрабатываются как истина, а четные целочисленные значения (младший бит ноль) рассматриваются как ложь.

Буквальная константа. TRUE. имеет целочисленное значение -1 и литеральную константу .FALSE. имеет целочисленное значение 0. Это представление используется Compaq Visual Fortran. Внутреннее представление значений LOGICAL не указано стандартом Fortran. Программы, которые используют целочисленные значения в контекстах LOGICAL или передают значения LOGICAL процедурам, написанным на других языках, являются непереносимыми и могут работать некорректно. Intel рекомендует избегать практики кодирования, которая зависит от внутреннего представления значений 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++ должно быть в порядке. Но для большей безопасности я бы просто использовал обычный char на стороне C++.

Наконец, из того, что я извлекаю из документации, в ifort есть некоторая встроенная поддержка взаимодействия с C, включая логические значения; Вы можете попытаться использовать это.

Ответ 2

Это то, что происходит, когда вы нарушаете контракт с языком и компилятором.

Возможно, вы где-то слышали, что "ноль - это ложь" и "ненулевое - это истина". Это верно, когда вы придерживаетесь языковых параметров, статически преобразуя int в bool или наоборот.

Он не работает, когда вы начинаете возиться с битными представлениями. В этом случае вы нарушаете свой контракт и вводите (по крайней мере) поведение, определяемое реализацией.

Просто не делай этого.

Это не до вас, как bool хранится в памяти. Это до компилятора. Если вы хотите изменить значение bool, либо назначьте true/false, либо назначьте целое число и используйте надлежащие механизмы преобразования, предоставленные C++.


Стандарт C++, используемый для фактического обращения к тому, как использовать bool таким образом, является непослушным, плохим и злым ("Использование значения bool способами, описанными в этом документе как" неопределенные ", например, путем изучения значения неинициализированного автоматического объекта может привести к тому, что он будет вести себя так, как если бы он не был ни true ни false. "), хотя он был удален в C++ 20 по редакционным причинам.