Почему #define TRUE (1 == 1) в логическом макросе C вместо простого как 1?

160

Я видел определения в C

#define TRUE (1==1)
#define FALSE (!TRUE)

Это необходимо? Какая польза от простого определения TRUE как 1 и FALSE как 0?

Роберт Харви
источник
35
И еще: #define TRUE (’/’/’/’); #define FALSE (’-’-’-’)(взято с сайта coding-guidelines.com/cbook/cbook1_1.pdf 871)
osgx
2
Нет, это паранойя по невежеству ... Вондердин, действительно. В C 1 и 0 при всех обстоятельствах одинаковы.
Дженс
@osgx Что это значит?
mrgloom

Ответы:

155

Этот подход будет использовать фактический booleanтип (и разрешать trueи false), если его поддерживает компилятор. (в частности, C ++)

Однако было бы лучше проверить, используется ли C ++ (с помощью __cplusplusмакроса), и использовать на самом деле trueи false.

В компиляторе C это эквивалентно 0и 1.
(обратите внимание, что удаление скобок нарушит порядок операций)

SLaks
источник
7
Это неправильно, bools здесь не используются. Результатом 1==1является int. (см. stackoverflow.com/questions/7687403/… .)
Мат
4
@Mat: Даже в C ++, с booleanтипом?
SLaks
9
Вопрос помечен C, но на самом деле в C ++ реляционные операторы возвращают trueили false.
Мат
5
@ Mat: Я предполагаю, что такой код написан в заголовках C, чтобы хорошо играть с C ++
SLaks
20
@SLaks Если она хочет играть хорошо с C ++, она #define TRUE trueи #define FALSE falseвсякий раз , когда __cplusplusопределяется.
Никос С.
137

Ответ - мобильность. Числовые значения TRUEи FALSEне важны. Что является важным является то , что такое заявление имеет if (1 < 2)значение if (TRUE)и заявление , как имеет if (1 > 2)значение if (FALSE).

Конечно, в C он (1 < 2)оценивает 1и (1 > 2)оценивает 0, так что, как говорили другие, нет никакой практической разницы в том, что касается компилятора. Но позволяя компилятору определять TRUEи в FALSEсоответствии с его собственными правилами, вы делаете их значения явными для программистов и гарантируете согласованность в своей программе и любой другой библиотеке (при условии, что другая библиотека соответствует стандартам C ... вы бы удивляться)


Немного истории
Некоторые бейсики определены FALSEкак 0и TRUEкак -1. Как и многие современные языки, они интерпретировали любое ненулевое значение как TRUE, но они оценивали логические выражения, которые были истинными как -1. Их NOTработа была реализована путем добавления 1 и переключения знака, потому что это было эффективно сделать таким образом. Так что «НЕ х» стало -(x+1). Побочным эффектом этого является то, что значение как 5оценивает TRUE, но NOT 5оценивает -6, что также TRUE! Находить такого рода ошибку не весело.

Наилучшие практики.
Учитывая фактические правила, что ноль интерпретируется как, FALSEа любое ненулевое значение интерпретируется как TRUE, вы никогда неTRUEFALSE должны сравнивать логически выглядящие выражения с или . Примеры:

if (thisValue == FALSE)  // Don't do this!
if (thatValue == TRUE)   // Or this!
if (otherValue != TRUE)  // Whatever you do, don't do this!

Зачем? Потому что многие программисты используют ярлык для обработки ints как bools. Они не одинаковы, но компиляторы обычно позволяют это. Так, например, совершенно законно писать

if (strcmp(yourString, myString) == TRUE)  // Wrong!!!

Это выглядит законно, и компилятор с радостью примет это, но, вероятно, не будет делать то, что вы хотите. Это потому , что возвращаемое значение strcmp()IS

      0 если yourString == myString
    <0 если yourString < myString
    > 0 еслиyourString > myString

Таким образом, строка выше возвращается TRUEтолько тогда, когда yourString > myString.

Правильный способ сделать это либо

// Valid, but still treats int as bool.
if (strcmp(yourString, myString))

или

// Better: lingustically clear, compiler will optimize.
if (strcmp(yourString, myString) != 0)

Так же:

if (someBoolValue == FALSE)     // Redundant.
if (!someBoolValue)             // Better.
return (x > 0) ? TRUE : FALSE;  // You're fired.
return (x > 0);                 // Simpler, clearer, correct.
if (ptr == NULL)                // Perfect: compares pointers.
if (!ptr)                       // Sleazy, but short and valid.
if (ptr == FALSE)               // Whatisthisidonteven.

Некоторые из этих «плохих примеров» вы часто найдете в рабочем коде, и многие опытные программисты клянутся ими: они работают, некоторые короче, чем их (педантично?) Правильные альтернативы, и идиомы почти повсеместно признаны. Но учтите: «правильные» версии не менее эффективны, они гарантированно переносимы, они будут проходить даже самые строгие линтеры, и даже новые программисты поймут их.

Разве это не стоит того?

Адам Лисс
источник
6
(1==1)не более портативный, чем 1. Собственные правила компилятора - это правила языка Си, который ясен и однозначен в отношении семантики равенства и операторов отношений. Я никогда не видел, чтобы компилятор ошибался.
Кит Томпсон
1
Фактически известно, что возвращаемое значение strcmpменьше, равно, равно или больше 0. Не гарантируется, что оно равно -1, 0 или 1, и существуют платформы, которые не возвращают эти значения, чтобы ускорить реализацию. Так что если strcmp(a, b) == TRUEтогда, a > bно обратное значение может не иметь места.
Мацей Пехотка
2
@KeithThompson - Возможно, «переносимость» была неправильным термином. Но факт остается фактом, что (1 == 1) является логическим значением; 1 нет.
Адам Лисс
2
@AdamLiss: В С, (1==1)и 1оба являются постоянными выражениями типа intсо значением 1. Они являются семантически идентичны. Я полагаю, вы можете написать код, который обслуживает читателей, которые этого не знают, но где это заканчивается?
Кит Томпсон
2
'not' 5, действительно, -6, на уровне битов.
woliveirajr
51

(1 == 1)Трюк полезен для определения TRUEтаким образом , что является прозрачным для C, но обеспечивает лучшее набрав в C ++. Тот же код можно интерпретировать как C или C ++, если вы пишете на диалекте под названием «Чистый C» (который компилируется как C или C ++) или если вы пишете заголовочные файлы API, которые могут использоваться программистами на C или C ++.

В переводных единицах C 1 == 1имеет точно такое же значение, как 1; и 1 == 0имеет то же значение, что и 0. Однако в модулях перевода C ++ 1 == 1есть тип bool. Таким образом, TRUEопределенный таким образом макрос лучше интегрируется в C ++.

Пример того, как он лучше интегрируется, состоит в том, что, например, если функция fooимеет перегрузки для intи для bool, то foo(TRUE)выберет boolперегрузку. Если TRUEэто просто определено как 1, то это не будет хорошо работать в C ++. foo(TRUE)захочет intперегрузку.

Конечно, C99 введены bool, trueи , falseи они могут быть использованы в заголовочных файлах , которые работают с C99 и с C.

Тем не мение:

  • эта практика определения TRUEи FALSEкак (0==0)и (1==0)предшествует C99.
  • есть все еще веские причины держаться подальше от C99 и работать с C90.

Если вы работаете в смешанном C и C ++ проекте, и не хотите , C99, определяет нижний регистр true, falseа boolвместо этого.

#ifndef __cplusplus
typedef int bool;
#define true (0==0)
#define false (!true)
#endif

При этом 0==0хитрость (была?) Использовалась некоторыми программистами даже в коде, который никогда не предназначался для взаимодействия с C ++ каким-либо образом. Это ничего не значит и говорит о том, что программист неправильно понимает, как логические функции работают в C.


В случае, если объяснение C ++ не было ясным, вот тестовая программа:

#include <cstdio>

void foo(bool x)
{
   std::puts("bool");  
}

void foo(int x)
{
   std::puts("int");  
}

int main()
{
   foo(1 == 1);
   foo(1);
   return 0;
}

Выход:

bool
int

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

Kaz
источник
3
Отличный и недооцененный ответ. Совершенно не очевидно, что два определения TRUEбудут различаться в C ++.
user4815162342
4
Как перегруженные функции относятся к коду, который компилируется как в C, так и в C ++?
Кит Томпсон
@KeithThompson Речь идет не только о перегрузке, но и о правильной типизации в целом. Перегрузка - это самый практичный пример, когда речь идет об игре. Конечно, код C ++ без перегрузок, шаблонов и всего этого «сложного» материала, удаленного для «C-совместимости» , на самом деле совсем не заботится о типах, но это не значит, что нужно отбрасывать ограничения концептуального типа в данном языке. ,
Кристиан Рау
1
@ChristianRau: Что ты имеешь в виду "не особо заботится о типах"? Типы являются центральными для языка Си; каждое выражение, значение и объект в программе на Си имеют четко определенный тип. Если вы хотите определить что-то по-разному в C и в C ++ (в тех редких случаях, когда вам действительно нужно написать код, который компилируется как в C, так и в C ++), вы можете использовать его #ifdef __cplusplusдля более ясного выражения своих намерений.
Кит Томпсон
@KeithThompson Да, я знаю, как важны типы. Просто без всяких вещей с учетом типов, таких как перегрузка и шаблоны, такие вещи, как различие между ними boolи intне имеют большого значения на практике, так как они неявно конвертируемы друг в друга (и в C фактически «одинаковы» , обратите внимание на кавычки) хотя) и не так много ситуаций, в которых вам действительно необходимо устранить неоднозначность между ними. «не так много» было, вероятно, слишком тяжело, «гораздо меньше по сравнению с кодом, использующим шаблоны и перегрузки», возможно, было бы лучше.
Кристиан Рау
18
#define TRUE (1==1)
#define FALSE (!TRUE)

эквивалентно

#define TRUE  1
#define FALSE 0

в С.

Результатом реляционных операторов является 0или 1. 1==1гарантированно будет оцениваться 1и !(1==1)гарантированно будет оцениваться 0.

Нет абсолютно никаких причин использовать первую форму. Обратите внимание, что первая форма, однако, не менее эффективна, поскольку почти во всех компиляторах константное выражение оценивается во время компиляции, а не во время выполнения. Это разрешено согласно этому правилу:

(C99, 6.6p2) «Постоянное выражение может оцениваться во время перевода, а не во время выполнения, и, соответственно, может использоваться в любом месте, где может быть константа».

PC-Lint будет даже выдавать сообщение (506, постоянное значение логическое) , если вы не используете Буквальное для TRUEи FALSEмакросов:

Для C TRUEдолжно быть определено 1. Однако в других языках используются значения, отличные от 1, поэтому некоторые программисты считают, что !0это безопасно.

Также в C99 stdbool.hопределены определения для логических макросов trueи false непосредственно используются литералы:

#define true   1
#define false  0
ouah
источник
1
У меня есть сомнение, TRUE заменяется при каждом использовании на 1 == 1, в то время как просто использование 1 заменит 1, не является ли первый метод дополнительными издержками сравнения ... или это сделано оптимизированным компилятором?
розовая пантера
4
Константные выражения @pinkpanther обычно оцениваются во время компиляции и поэтому не вызывают никаких накладных расходов.
13
2
1==1гарантированно будет оцениваться по1
ouah
3
@NikosC. Это хороший вопрос. Это имеет значение для кода формы if(foo == true), который превратится из просто плохой практики в плоскую ошибку.
Джечлин
1
+1 за указание на опасность (x == TRUE)может иметь другое значение истинности, чем x.
Джошуа Тейлор
12

Помимо C ++ (уже упоминалось), еще одним преимуществом являются инструменты статического анализа. Компилятор покончит с любой неэффективностью, но статический анализатор может использовать свои собственные абстрактные типы, чтобы различать результаты сравнения и другие целочисленные типы, поэтому он неявно знает, что TRUE должен быть результатом сравнения и не должен считаться совместимым с целым числом.

Очевидно, что C говорит, что они совместимы, но вы можете запретить преднамеренное использование этой функции, чтобы помочь выявить ошибки - например, когда кто-то может запутаться &и &&, или они испортили свой операторский приоритет.

sh1
источник
1
Это хороший момент, и, возможно, некоторые из этих инструментов могут даже перехватывать глупый код if (boolean_var == TRUE) путем расширения, в if (boolean_var == (1 == 1))которое благодаря расширенной информации о типе (1 == 1)узла попадает шаблон if (<*> == <boolean_expr>).
Каз
4

Практическая разница - нет. 0оценивается falseи 1оценивается true. Тот факт, что вы используете логическое выражение ( 1 == 1) или 1, для определения true, не имеет никакого значения. Они оба получают оценку int.

Обратите внимание , что стандартная библиотека C предоставляет специальный заголовок для определения булевых: stdbool.h.

башмак
источник
конечно нет ... но некоторые люди могут думать иначе, особенно для отрицательных чисел, поэтому :)
pinkpanther
Какой? У тебя это задом наперед. trueоценивается 1и falseоценивается 0. Си не знает о родных булевых типах, они просто целые числа.
Джечлин
В C операторы отношений и равенства дают результаты типа intсо значением 0или 1. C действительно имеет логический тип ( _Boolс макросом, boolопределенным в <stdbool.h>, но он был добавлен только в C99, который не изменил семантику операторов для использования нового типа.
Кит Томпсон,
@djechlin: По стандарту 1999, C делает есть родной логический тип. Это называется _Boolи <stdbool.h>имеет #define bool _Bool.
Кит Томпсон
@KeithThompson, вы правы насчет того, 1 == 1что вас оценивают как int. Ред.
Чистка
3

Мы не знаем точного значения, которому равно TRUE, и компиляторы могут иметь свои собственные определения. Итак, что вы привели, так это использование внутреннего компилятора для определения. Это не всегда необходимо, если у вас есть хорошие навыки программирования, но вы можете избежать проблем из-за плохого стиля программирования, например:

if ((a> b) == ИСТИНА)

Это может быть катастрофой, если вы вручную определите TRUE как 1, а внутреннее значение TRUE - другое.

capiggue
источник
В С > оператор всегда выдает 1 для истины, 0 для ложных. Нет никакой возможности того, чтобы компилятор C понял это неправильно. Сравнение равенства TRUEи FALSEплохой стиль; вышесказанное более четко написано как if (a > b). Но идея о том, что разные компиляторы C могут по-разному трактовать истину и ложь, неверна.
Кит Томпсон,
2
  1. Элемент списка

Обычно на языке программирования C 1 определяется как true, а 0 определяется как false. Поэтому вы часто видите следующее:

#define TRUE 1 
#define FALSE 0

Однако любое число, не равное 0, будет также оценено как истинное в условном выражении. Поэтому, используя ниже:

#define TRUE (1==1)
#define FALSE (!TRUE)

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

Сабашан Рагаван
источник
4
Я бы не назвал это «безопасным делом» - скорее, вы даете себе ложное чувство безопасности.
dodgethesteamroller