Если я получаю переменную 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
не совсем дубликат Разрешает ли стандарт С++ неинициализированному bool вызывать сбой программы?, где мой ответ объясняет, что x86-64 System V ABI указывает, что bool
равно 0 или 1, и поэтому компилятору разрешено предполагать это при создании кода.
Существует противоречие между математикой логических значений и их представлением в информатике. Математически логические значения имеют два значения, то есть один бит. Проблема в том, что в C++ логические значения должны быть адресуемыми, а отдельные биты — нет. Стандарт требует, чтобы все логические операции приводили к нулю или единице. Все остальное является несовместимой реализацией. Стандарт также требует, чтобы программисты следовали этому правилу. В частности, преднамеренная установка битов таким образом, чтобы логическое значение имело значение, которое не равно ни нулю, ни единице, является неопределенным поведением.
Вот минимальный пример. GCC до 8.3 ведет себя так же, как в посте. С 9.1 по другому, но все равно удивительно. (С++ — это весело!)
ВЫВОД: не используйте логические/логические типы при использовании нескольких языков в программе. Каждый язык имеет свой собственный стандарт битов bool. Используйте int или другие типы, которые имеют одинаковое определение на всех языках (если это вообще возможно)
«Доктор, мне больно, когда я это делаю».
@DennisWilliamson Если «это» - «ходьба», разумно потребовать объяснения, а не ответа «не ходи тогда» от вашего врача.
В 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 neithertrue
norfalse
(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
для «граничного» кода и маршалируйте его как целое число соответствующего размера. Это будет работать в условных выражениях и ко. так же хорошо.
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 typeTYPE
shall occupyN
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 asbool
, but in fortran if bits are 00000011 it istrue
, 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 бит, стандарт должен четко определять, что происходит, когда установлены другие биты. Вы не можете предполагать, что люди никогда не прикоснутся к другим битам. Люди разрабатывают программное обеспечение, сочетая множество языков, и вам нужно безопасно охватить эти случаи. Неопределенное поведение, поскольку некоторые биты установлены в единицу, на мой взгляд, просто неприемлемый стандарт. Я называю это дурацким стандартом.
Если вы будете использовать этот стандарт, логическое значение должно быть 1 бит, а не 8 бит.
Это похоже на 8-полосное шоссе, все полосы открыты, но если вы используете любую полосу, кроме первой, вы гарантированно попадете в аварию.
bool
эффективно является один бит, просто это не реализовано под капотом. Сколько битов используется для представления, является деталью реализации (частью ABI), а не той, которая определяется языком. Я не понимаю, почему это проблема в реальном мире, хотя это делает отличные вопросы и ответы по переполнению стека. Я много взаимодействую между C++ и кодом на других языках, и у меня никогда не было проблем. @BY408
Попробуйте использовать gcc с компилятором Intel Fortran. Имейте функцию fortran, которая возвращает переменную логического * 1 типа, которая является 8-битным логическим значением, и назначьте ее логическому значению С++ и посмотрите, что произойдет:)
Я не являюсь экспертом по Фортрану при любом натяжении воображения, но я не понимаю, почему это может быть проблемой. Насколько я могу судить, переменная типа LOGICAL*1
имеет семантику, совместимую с bool C++: ненулевые значения являются «истинными», а нулевые значения — «ложными». Я полагать, что Fortran фактически представляет «true», устанавливая все биты, тогда как реализация C++ обычно беспокоится только об установке самого младшего бита, но это не имеет значения, пока «false» определяется как «все биты равны нулю». ". Кроме того, есть способы сделать Intel Fortran полностью совместимым с C; смотрите logical(c_bool)
. @BY408
@Cody Grey, логическая * 1 на фортране имеет то же представление, что и bool, но в фортране, если биты равны 00000011, это правда, в C++ это не определено. Это проблема. Вы не можете заставить fortran возвращать логическую *1 и присваивать ее логическому значению C++. С++ bool становится неопределенным, см. мой код выше. (Я манипулирую битами, чтобы имитировать то, что делает fortran и как его обрабатывает C++)
@ BY408 Похоже, вы не знаете, как правильно взаимодействовать между fortran и C++. Не вините в своем невежестве стандарт C++. Стандарты существуют точно, потому что без них любая реализация могла бы делать все, что им не нужно, и интероперабельность была бы невозможна.
Кстати, такая интероперабельность не является стандартным бизнесом 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
.
Кроме того, в нем также говорится, что LOGICAL
должно иметь только 1 и 0 как возможные значения, что заставляет меня задуматься, как вы получили поврежденные данные даже на стороне Фортрана: насколько я понимаю, LOGICAL
должен работать аналогично C++ bool
: фактическое содержание всегда 0 или 1, это по заданию, что ненулевые целые значения принудительно становятся 1. Но опять же, это только то, что я, кажется, понял из быстрого чтения спецификации, поэтому я могу ошибаться.
Кажется, на это намекает последнее сообщение этого поста: даже в Фортране вы не должны возиться с битами LOGICAL
, устанавливая для них любое значение, которое вам нравится.
@ BY408: начиная с этого, я немного расширил свой ответ, вам может быть полезно знать о некоторых дополнительных виновниках в отношении LOGICAL
, совместимости и даже переносимости внутри самого Fortran.
Функция Fortran добавлена выше.
повторите цитату о bool
вознях (к сожалению, модератор удалил наши предыдущие комментарии по этой теме без всякой причины): github.com/cplusplus/draft/commit/… #sadface
@LightnessRacesinOrbit: как только у нас появилась заметка, которая не добавила путаницы, они решили ее удалить! :-(
@MatteoItalia ИКР!
Вот что происходит, когда вы нарушаете свой контракт как с языком, так и с компилятором.
Вы, наверное, где-то слышали, что «ноль — это ложь», а «ненулевое — это правда». Это справедливо, когда вы придерживаетесь параметров языка, статически преобразовывая int
в bool
или наоборот.
Это не работает, когда вы начинаете возиться с битовыми представлениями. В этом случае вы нарушаете свой контракт и входите в область (как минимум) поведения, определяемого реализацией.
Просто не делай этого.
Вам не решать, как bool
хранится в памяти. Это зависит от компилятора. Если вы хотите изменить значение bool
, либо присвойте true
/false
, либо присвойте целое число и используйте соответствующие механизмы преобразования, предоставляемые C++.
Стандарт C++ фактически давал конкретный призыв к тому, что использование bool
таким образом непослушно, плохо и зло («Использование значения bool
способами, описанными в этом документе как «неопределенное», например, путем проверки значения неинициализированного автоматического объекта, может привести к тому, что он будет вести себя так, как будто он не является ни true
, ни false
».), хотя это было удалено в C++20 по редакционным причинам.
bool
хранится в памяти, что указано в ABI (кроме частных bool
объектов, которые никогда не может видеть ничто за пределами функции; тогда правило «как если» действует в полную силу). «Вплоть до компилятора» — полезное упрощение, но на самом деле это не так, особенно для компиляторов, отличных от GCC (поскольку разработчики GCC разработали x86-64 System V ABI).
@PeterCordes Это правильно. Как вы сказали, это полезное упрощение и полностью оправдано в этом контексте, ИМО.
Однако мой ответ на вопрос UB с неинициализированным логическим значением, на который вы ссылались, охватывает все это :) +1 не связывайтесь с объектным представлением bool
.
Я не буду путать биты в своем коде, но эта проблема возникает из-за того, что у меня есть функция на фортране, которая возвращает логический тип (который имеет тот же размер, что и bool) и присваивает это возвращаемое значение логическому типу C++. Затем C++ начал действовать так же, как и выше. Я добавил эту битовую манипуляцию, чтобы имитировать то, что fortran возвращает как true, чтобы избавить вас от необходимости использовать два компилятора для воспроизведения проблемы. То, что fortran присваивает переменной как true, неверно, когда вы возвращаете это и назначаете его логическому значению C++. И не интуитивно понятно, почему стандарт не принимает ненулевые биты как истинные.
@ BY408, если вам нужен тип для обработки значений, отличных от 1 и 0, вам не следует использовать логическое значение. Используйте unsigned char или int или другую числовую переменную. Символ без знака, используемый в операторе if (), ведет себя так, как вы хотите (ноль — это ложь, ненулевое значение — это правда).
@ BY408 Такое правило обязательно сделало бы код медленнее без реальной выгоды. Маттео показывает, как реализации, разрешенные стандартом, могут быть очень быстрыми! Спасибо, стандарт!
@ BY408, можете ли вы показать код этой функции Fortran?
@LightnessRacesinOrbit: если бы в стандарте было указано, что каждое чтение bool
, которое имеет ненулевое четное число в bool
, может независимо рассматривать его как получение любого целочисленного значения, и указано, что реализации не должны разрешать коду принимать адрес объектов __bool
, что сделало бы тип совместимым с ранее существовавшими типами bit
многих реализаций и позволило бы компиляторам генерировать оптимальный код во многих случаях, когда они в настоящее время не могут.
@supercat: я не понимаю. Можешь перефразировать?
@LightnessRacesinOrbit: предположим, что unsigned f(unsigned)
является внешней функцией, возвращаемое значение которой, как известно программисту, всегда равно 0 или 1 при передаче аргумента 2. Учитывая bool b = f(2);
, компилятору C99 потребуется сгенерировать машинный код, чтобы проверить, равно ли значение нулю и установите его на 1, если нет. Если бы Стандарт был написан с учетом оптимизации, он мог бы просто хранить возвращаемое значение f
напрямую. Кроме того, довольно много компиляторов до C99 имели тип bit
, который вел себя как однобитовое битовое поле с тем же поведением усечения, что и другие беззнаковые типы.
@supercat Это кажется крайним случаем по сравнению с каждое преобразование int-to-bool когда-либо.
@LightnessRacesinOrbit: та же проблема будет применяться в любой ситуации, когда программист знает, что значение целочисленного типа будет 0 или 1 во всех случаях, когда значение в bool
будет иметь значение, но компилятор не сможет это узнать.
@supercat Есть встроенные функции, которые вы можете использовать для таких крайних случаев, чтобы сигнализировать, что вы знаете что-то, чего не знает компилятор.
С неопределенным поведением возможно все :)