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

Если я получаю переменную bool и устанавливаю ее второй бит в 1, тогда переменная оценивается как истина и ложь одновременно. Скомпилируйте следующий код с помощью gcc6.3 с опцией -g (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

Это может произойти, когда вы вызываете функцию на другом языке (скажем, на фортране), где определения true и false отличаются от C++. Для фортрана, если какие-либо биты не равны 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;
  }


}

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

       logical*1 function return_true()
         implicit none

         return_true = 1;

       end function return_true

С неопределенным поведением возможно все :)

Jeremy Friesner 30.05.2019 00:06

не совсем дубликат Разрешает ли стандарт С++ неинициализированному bool вызывать сбой программы?, где мой ответ объясняет, что x86-64 System V ABI указывает, что bool равно 0 или 1, и поэтому компилятору разрешено предполагать это при создании кода.

Peter Cordes 30.05.2019 13:02

Существует противоречие между математикой логических значений и их представлением в информатике. Математически логические значения имеют два значения, то есть один бит. Проблема в том, что в C++ логические значения должны быть адресуемыми, а отдельные биты — нет. Стандарт требует, чтобы все логические операции приводили к нулю или единице. Все остальное является несовместимой реализацией. Стандарт также требует, чтобы программисты следовали этому правилу. В частности, преднамеренная установка битов таким образом, чтобы логическое значение имело значение, которое не равно ни нулю, ни единице, является неопределенным поведением.

David Hammen 30.05.2019 14:05

Вот минимальный пример. GCC до 8.3 ведет себя так же, как в посте. С 9.1 по другому, но все равно удивительно. (С++ — это весело!)

Alexander Malakhov 30.05.2019 18:30

ВЫВОД: не используйте логические/логические типы при использовании нескольких языков в программе. Каждый язык имеет свой собственный стандарт битов bool. Используйте int или другие типы, которые имеют одинаковое определение на всех языках (если это вообще возможно)

BY408 30.05.2019 21:03

«Доктор, мне больно, когда я это делаю».

Dennis Williamson 31.05.2019 00:20

@DennisWilliamson Если «это» - «ходьба», разумно потребовать объяснения, а не ответа «не ходи тогда» от вашего врача.

Lightness Races in Orbit 03.06.2019 17:40
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
43
7
6 840
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

Если вы установите для него значение, отличное от допустимого (в данном конкретном случае, сменив bool на char и изменив его битовое представление), вы нарушите правила языка, так что может случиться что угодно. В частности, в стандарте прямо указано, что «сломанный» bool может вести себя как true, так и false (или ни true, ни false) одновременно:

Using a bool value in ways described by this International Standard as “undefined,” such as by examining the value of an uninitialized automatic object, might cause it to behave as if it is neither true nor 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 T = 3 действительно безопасен, а T в конечном итоге окажется с true во внутренностях .

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


Обновление о стороне проблемы, связанной с Fortran/совместимостью

Disclaimer all I know of Fortran is what I read this morning on standard documents, and that I have some punched cards with Fortran listings that I use as bookmarks, so go easy on me.

Во-первых, такого рода языковая совместимость не является частью языковых стандартов, а является частью платформы 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 сказано, что

The “TYPE*N” notation specifies that variables or aggregate members of type TYPE shall occupy N bytes of storage.

(там же.)

Для LOGICAL*1 нет явно указанного эквивалентного типа, и это понятно: это даже не стандарт; действительно, если вы попытаетесь скомпилировать программу на Фортране, содержащую 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 также указывает:

The values for type LOGICAL are .TRUE. implemented as 1 and .FALSE. implemented as 0.

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

A fortran logical*1 has same representation as bool, but in fortran if bits are 00000011 it is true, in C++ it is undefined.

Это последнее утверждение неверно, стандарт 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 и межъязыковой совместимости:

Specifies that integers with a non-zero value are treated as true, integers with a zero value are treated as false. The literal constant .TRUE. has an integer value of 1, and the literal constant .FALSE. has an integer value of 0. This representation is used by Intel Fortran releases before Version 8.0 and by Fortran PowerStation.

The default is fpscomp nologicals, which specifies that odd integer values (low bit one) are treated as true and even integer values (low bit zero) are treated as false.

The literal constant .TRUE. has an integer value of -1, and the literal constant .FALSE. has an integer value of 0. This representation is used by Compaq Visual Fortran. The internal representation of LOGICAL values is not specified by the Fortran standard. Programs which use integer values in LOGICAL contexts, or which pass LOGICAL values to procedures written in other languages, are non-portable and may not execute correctly. Intel recommends that you avoid coding practices that depend on the internal representation of LOGICAL values.

(выделение добавлено)

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

Спасибо за подробное объяснение. Я думаю, что стандартное определение С++ очень опасно и оставляет много места для ошибок при использовании с другими языками. Fortran определяет bool как true, если какой-либо бит равен 1, и как false, если все биты равны нулю. Если bool имеет 8 бит, стандарт должен четко определять, что происходит, когда установлены другие биты. Вы не можете предполагать, что люди никогда не прикоснутся к другим битам. Люди разрабатывают программное обеспечение, сочетая множество языков, и вам нужно безопасно охватить эти случаи. Неопределенное поведение, поскольку некоторые биты установлены в единицу, на мой взгляд, просто неприемлемый стандарт. Я называю это дурацким стандартом.

BY408 30.05.2019 19:50

Если вы будете использовать этот стандарт, логическое значение должно быть 1 бит, а не 8 бит.

BY408 30.05.2019 19:51

Это похоже на 8-полосное шоссе, все полосы открыты, но если вы используете любую полосу, кроме первой, вы гарантированно попадете в аварию.

BY408 30.05.2019 19:53
bool эффективно является один бит, просто это не реализовано под капотом. Сколько битов используется для представления, является деталью реализации (частью ABI), а не той, которая определяется языком. Я не понимаю, почему это проблема в реальном мире, хотя это делает отличные вопросы и ответы по переполнению стека. Я много взаимодействую между C++ и кодом на других языках, и у меня никогда не было проблем. @BY408
Cody Gray 30.05.2019 19:53

Попробуйте использовать gcc с компилятором Intel Fortran. Имейте функцию fortran, которая возвращает переменную логического * 1 типа, которая является 8-битным логическим значением, и назначьте ее логическому значению С++ и посмотрите, что произойдет:)

BY408 30.05.2019 19:56

Я не являюсь экспертом по Фортрану при любом натяжении воображения, но я не понимаю, почему это может быть проблемой. Насколько я могу судить, переменная типа LOGICAL*1 имеет семантику, совместимую с bool C++: ненулевые значения являются «истинными», а нулевые значения — «ложными». Я полагать, что Fortran фактически представляет «true», устанавливая все биты, тогда как реализация C++ обычно беспокоится только об установке самого младшего бита, но это не имеет значения, пока «false» определяется как «все биты равны нулю». ". Кроме того, есть способы сделать Intel Fortran полностью совместимым с C; смотрите logical(c_bool). @BY408

Cody Gray 31.05.2019 01:36

@Cody Grey, логическая * 1 на фортране имеет то же представление, что и bool, но в фортране, если биты равны 00000011, это правда, в C++ это не определено. Это проблема. Вы не можете заставить fortran возвращать логическую *1 и присваивать ее логическому значению C++. С++ bool становится неопределенным, см. мой код выше. (Я манипулирую битами, чтобы имитировать то, что делает fortran и как его обрабатывает C++)

BY408 31.05.2019 02:27

@ BY408 Похоже, вы не знаете, как правильно взаимодействовать между fortran и C++. Не вините в своем невежестве стандарт C++. Стандарты существуют точно, потому что без них любая реализация могла бы делать все, что им не нужно, и интероперабельность была бы невозможна.

Giacomo Alzetta 31.05.2019 08:56

Кстати, такая интероперабельность не является стандартным бизнесом C++ или Fortran, а связана с ABI платформы. В случае Linux x86-64 это uclibc.org/docs/psABI-x86_64.pdf; он указывает, что простое LOGICAL сопоставляется с signed int (так что это 4 байта, как Win32 BOOL), в то время как для LOGICAL*1 нет явного сопоставления. Из того факта, что «обозначение «TYPE*N» указывает, что переменные или совокупные члены типа TYPE должны занимать N байт памяти», должно следовать, что отображаемый тип, вероятно, должен быть signed char.

Matteo Italia 31.05.2019 09:25

Кроме того, в нем также говорится, что LOGICAL должно иметь только 1 и 0 как возможные значения, что заставляет меня задуматься, как вы получили поврежденные данные даже на стороне Фортрана: насколько я понимаю, LOGICAL должен работать аналогично C++ bool: фактическое содержание всегда 0 или 1, это по заданию, что ненулевые целые значения принудительно становятся 1. Но опять же, это только то, что я, кажется, понял из быстрого чтения спецификации, поэтому я могу ошибаться.

Matteo Italia 31.05.2019 09:27

Кажется, на это намекает последнее сообщение этого поста: даже в Фортране вы не должны возиться с битами LOGICAL, устанавливая для них любое значение, которое вам нравится.

Matteo Italia 31.05.2019 09:32

@ BY408: начиная с этого, я немного расширил свой ответ, вам может быть полезно знать о некоторых дополнительных виновниках в отношении LOGICAL, совместимости и даже переносимости внутри самого Fortran.

Matteo Italia 31.05.2019 15:07

Функция Fortran добавлена ​​выше.

BY408 31.05.2019 21:08

повторите цитату о bool вознях (к сожалению, модератор удалил наши предыдущие комментарии по этой теме без всякой причины): github.com/cplusplus/draft/commit/… #sadface

Lightness Races in Orbit 10.06.2019 16:41

@LightnessRacesinOrbit: как только у нас появилась заметка, которая не добавила путаницы, они решили ее удалить! :-(

Matteo Italia 10.06.2019 17:17

@MatteoItalia ИКР!

Lightness Races in Orbit 10.06.2019 17:28

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

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

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

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

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


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

Это зависит от компилятора. Это зависит от "реализации" в терминах C++. На большинстве платформ (включая x86-64 GNU/Linux) все компиляторы следуют ABI (x86-64 System V), который является отдельным от компилятора документом. Это нет зависит от компилятора, как bool хранится в памяти, что указано в ABI (кроме частных bool объектов, которые никогда не может видеть ничто за пределами функции; тогда правило «как если» действует в полную силу). «Вплоть до компилятора» — полезное упрощение, но на самом деле это не так, особенно для компиляторов, отличных от GCC (поскольку разработчики GCC разработали x86-64 System V ABI).
Peter Cordes 30.05.2019 13:14

@PeterCordes Это правильно. Как вы сказали, это полезное упрощение и полностью оправдано в этом контексте, ИМО.

Lightness Races in Orbit 30.05.2019 13:16

Однако мой ответ на вопрос UB с неинициализированным логическим значением, на который вы ссылались, охватывает все это :) +1 не связывайтесь с объектным представлением bool.

Peter Cordes 30.05.2019 13:16

Я не буду путать биты в своем коде, но эта проблема возникает из-за того, что у меня есть функция на фортране, которая возвращает логический тип (который имеет тот же размер, что и bool) и присваивает это возвращаемое значение логическому типу C++. Затем C++ начал действовать так же, как и выше. Я добавил эту битовую манипуляцию, чтобы имитировать то, что fortran возвращает как true, чтобы избавить вас от необходимости использовать два компилятора для воспроизведения проблемы. То, что fortran присваивает переменной как true, неверно, когда вы возвращаете это и назначаете его логическому значению C++. И не интуитивно понятно, почему стандарт не принимает ненулевые биты как истинные.

BY408 31.05.2019 02:55

@ BY408, если вам нужен тип для обработки значений, отличных от 1 и 0, вам не следует использовать логическое значение. Используйте unsigned char или int или другую числовую переменную. Символ без знака, используемый в операторе if (), ведет себя так, как вы хотите (ноль — это ложь, ненулевое значение — это правда).

JPhi1618 31.05.2019 06:30

@ BY408 Такое правило обязательно сделало бы код медленнее без реальной выгоды. Маттео показывает, как реализации, разрешенные стандартом, могут быть очень быстрыми! Спасибо, стандарт!

Lightness Races in Orbit 31.05.2019 12:19

@ BY408, можете ли вы показать код этой функции Fortran?

Matteo Italia 31.05.2019 15:55

@LightnessRacesinOrbit: если бы в стандарте было указано, что каждое чтение bool, которое имеет ненулевое четное число в bool, может независимо рассматривать его как получение любого целочисленного значения, и указано, что реализации не должны разрешать коду принимать адрес объектов __bool , что сделало бы тип совместимым с ранее существовавшими типами bit многих реализаций и позволило бы компиляторам генерировать оптимальный код во многих случаях, когда они в настоящее время не могут.

supercat 31.05.2019 17:14

@supercat: я не понимаю. Можешь перефразировать?

Lightness Races in Orbit 31.05.2019 17:15

@LightnessRacesinOrbit: предположим, что unsigned f(unsigned) является внешней функцией, возвращаемое значение которой, как известно программисту, всегда равно 0 или 1 при передаче аргумента 2. Учитывая bool b = f(2);, компилятору C99 потребуется сгенерировать машинный код, чтобы проверить, равно ли значение нулю и установите его на 1, если нет. Если бы Стандарт был написан с учетом оптимизации, он мог бы просто хранить возвращаемое значение f напрямую. Кроме того, довольно много компиляторов до C99 имели тип bit, который вел себя как однобитовое битовое поле с тем же поведением усечения, что и другие беззнаковые типы.

supercat 31.05.2019 17:40

@supercat Это кажется крайним случаем по сравнению с каждое преобразование int-to-bool когда-либо.

Lightness Races in Orbit 31.05.2019 17:42

@LightnessRacesinOrbit: та же проблема будет применяться в любой ситуации, когда программист знает, что значение целочисленного типа будет 0 или 1 во всех случаях, когда значение в bool будет иметь значение, но компилятор не сможет это узнать.

supercat 31.05.2019 17:48

@supercat Есть встроенные функции, которые вы можете использовать для таких крайних случаев, чтобы сигнализировать, что вы знаете что-то, чего не знает компилятор.

Lightness Races in Orbit 31.05.2019 17:56

Другие вопросы по теме