Установка дополнительных битов в bool делает его истинным и ложным одновременно - PullRequest
40 голосов
/ 30 мая 2019

Если я получу переменную 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)где истинное и ложное определение отличается от 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

Ответы [ 2 ]

63 голосов
/ 30 мая 2019

В C ++ битовое представление (и даже размер) bool определяется реализацией;как правило, он реализован в виде типа char, принимающего в качестве возможных значений 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"trust" равен 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 (которыйопределяется как C ++ bool в примечании 3.1.2 †) имеет какую-либо совместимость с Fortran LOGICAL;в частности, в 9.2.2 таблица 9.2 указывает, что «обычный» LOGICAL отображается на signed intTYPE*N типах говорится, что

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

(там же)

Для LOGICAL*1 явно не указан эквивалентный тип, и это понятно: он даже не стандартный;действительно, если вы попытаетесь скомпилировать программу на Фортране, содержащую LOGICAL*1 в режиме совместимости с Фортраном 95, вы получите предупреждения об этом как по ifort

./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, чтобы быть в безопасности.8 * Однако : 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

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

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

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

По умолчанию используется значение 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 daТо есть в значения LOGICAL, так как это плохо даже в Fortran, и убедитесь, что вы используете правильный флаг компилятора, чтобы получить ABI-совместимое представление для логических значений, и совместимость с C / C ++ должна быть в порядке.Но для большей безопасности я бы просто использовал обычный char на стороне C ++.

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

22 голосов
/ 30 мая 2019

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

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

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

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

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


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

...