Почему тернарный оператор используется для определения 1 и 0 в макросе?


79

Я использую SDK для встроенного проекта. В этом исходном коде я нашел код, который, по крайней мере, мне показался странным. Во многих местах в SDK есть исходный код в таком формате:

#define ATCI_IS_LOWER( alpha_char )  ( ( (alpha_char >= ATCI_char_a) && (alpha_char <= ATCI_char_z) ) ? 1 : 0 )

#define ATCI_IS_UPPER( alpha_char )  ( ( (alpha_char >= ATCI_CHAR_A) && (alpha_char <= ATCI_CHAR_Z) ) ? 1 : 0 )

Имеет ли значение использование тернарного оператора здесь?

Не

#define FOO (1 > 0)

такой же как

#define BAR ( (1 > 0) ? 1 : 0)

?

Я попытался оценить это, используя

printf("%d", FOO == BAR);

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


8
Нет, нет причин. Ты прав.
Art

29
Частично не по теме: когда закончится безумие использования препроцессора? Здесь возможна множественная оценка задействованных функций. Просто ненужно.
Штефан

3
Иногда тоже приятно быть откровенным. Тернарный оператор здесь сразу дает понять, что цель макроса - вернуть логическое значение.
pipe

5
По крайней мере, макросы следует использовать (alpha_char)вместо alpha_char, просто чтобы убедиться, что он не сломается, если кто-то попробует что-то безумноеATCI_IS_LOWER(true || -1) .
Джастин Тайм - Восстановить Монику

5
Похоже, типа CI написал давно. Я пришел к С от Паскаля, который имел специальный booleanтип, так что я впустую невыразимое время изменения ужасов , как if (n)к if (0 != n), возможно добавлением сомнительной слепок « чтобы убедиться , что». Я уверен, что пуленепробиваемое неравенство мне if (a < b) ...тоже нравится . Конечно, это было похоже на Паскаля if a < b then ..., но я знал, что C - < это не a, booleanа an int, а an intможет быть почти любым ! Страх ведет к позолоте, позолота приводит к паранойе, паранойя приводит к ... подобному коду.
Кевин Дж. Чейз

Ответы:


131

Вы правы, в C тавтологично. Обе ваши конкретные тернарные условные и (1 > 0) имеют тип int.

Но в C ++ это имело бы значение в некоторых любопытных угловых случаях (например, в качестве параметров для перегруженных функций), поскольку ваше тернарное условное выражение имеет тип int, тогда как (1 > 0)имеет тип bool.

Я предполагаю, что автор задумался над этим, чтобы сохранить совместимость с C ++.


2
Я думал, что bool <-> intпреобразования неявны в C ++ согласно §4.7 / 4 из стандарта (интегральное преобразование), так какое это имеет значение?
Motun

70
Рассмотрим две перегрузки функции foo, одна из которых принимает const bool&другой const int&. Один из них вам платит, другой переформатирует ваш жесткий диск. В этом случае вы можете убедиться, что вызываете правильную перегрузку.
Вирсавия

3
Разве не было бы более очевидным обработать этот случай путем приведения результата к, intа не с использованием троичного?
martinkunev

18
@Bathsheba В то время как законный угловой случай, любой программист, который использует интегральные перегрузки для реализации такого непоследовательного поведения, является полностью злым.
JAB

7
@JAB: вам не нужно быть злым, вам просто нужно совершить (распространенную) ошибку, написав фрагмент кода, который случайно выполняет две разные вещи (или, что еще хуже, вызывает неопределенное поведение ) в зависимости от интегрального типа, и иметь неудача сделать это в месте, которое может запускать радикально разные пути кода.

28

Существуют инструменты линтинга, которые считают, что результат сравнения является логическим и не может использоваться непосредственно в арифметике.

Не называть имен и не указывать пальцем, но PC-lint - такой инструмент для линтинга .

Я не говорю, что они правы, но это возможное объяснение того, почему код был написан именно так.


10
Not to name names or point any fingers,но ты вроде как сделал и то, и другое, лол.
StackOverflowed

19

Иногда вы увидите это в очень старом коде, еще до того, как появился стандарт C, который (x > y)оценивает числовые значения 1 или 0; некоторые процессоры предпочли бы вместо этого вычислять -1 или 0, а некоторые очень старые компиляторы могли просто последовать их примеру, поэтому некоторые программисты посчитали, что им нужна дополнительная защита.

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

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) ? 1 : 0)

внутреннее &-выражение оценивается либо как 0, либо как числовое значение F_DO_GRENFELZ, которое, вероятно, не равно 1, поэтому ? 1 : 0служит для его канонизации. Я лично считаю, что проще написать это как

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) != 0)

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


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

1
@PJTraill Каждый раз, когда вы ставите пробелы внутри скобок, Бог убивает котенка. Пожалуйста. Подумайте о котятах.
zwol 06

Это лучшая причина, по которой я слышал, что нельзя помещать пробелы в скобки в программе на языке C.
PJTraill

15

В коде SDK есть ошибка, и тройка, вероятно, была кладжем для ее исправления.

В качестве макроса аргументы (alpha_char) могут быть любым выражением и должны быть заключены в круглые скобки, потому что такие выражения, как 'A' && 'c' не пройдут проверку.

#define IS_LOWER( x ) ( ( (x >= 'a') && (x <= 'z') ) ?  1 : 0 )
std::cout << IS_LOWER('A' && 'c');
**1**
std::cout << IS_LOWER('c' && 'A');
**0**

Вот почему всегда следует заключать в скобки аргументы макроса в раскрытии.

Итак, в вашем примере (но с параметрами) они оба ошибаются.

#define FOO(x) (x > 0)
#define BAR(x) ((x > 0) ? 1 : 0)

Правильнее всего их заменить на

#define BIM(x) ((x) > 0)

@CiaPan Обращает внимание на то, что следующий комментарий состоит в том, что использование параметра более одного раза приводит к неопределенным результатам. Например

#define IS_LOWER( x ) (((x) >= 'a') && ((x) <= 'z'))
char ch = 'y';
std::cout << IS_LOWER(ch++);
**1** 
**BUT ch is now '{'**

4
Другая ошибка заключается в том, что параметр используется дважды, поэтому аргумент с побочными эффектами приведет к непредсказуемым результатам: IS_LOWER(++ var)может увеличиваться varодин или два раза, кроме того, он может не заметить и не распознать нижний регистр, 'z'если он varбыл 'y'до вызова макроса. Вот почему следует избегать таких макросов или просто передавать аргумент функции.
CiaPan

5

В C это не имеет значения. Логические выражения в C имеют тип intи значение, равное 0или 1, поэтому

ConditionalExpr ? 1 : 0

не имеет никакого эффекта.

В C ++ это фактически преобразование int, потому что условные выражения в C ++ имеют тип bool.

#include <stdio.h>
#include <stdbool.h>

#ifndef __cplusplus

#define print_type(X) _Generic(X, int: puts("int"), bool: puts("bool") );

#else
template<class T>
int print_type(T const& x);
template<> int print_type<>(int const& x) { return puts("int"); }
template<> int print_type<>(bool const& x) { return puts("bool"); }


#endif

int main()
{
    print_type(1);
    print_type(1 > 0);
    print_type(1 > 0 ? 1 : 0);

/*c++ output:
  int 
  int 
  int

  cc output:
  int
  bool
  int
*/

}

Также возможно, что никакого эффекта не предполагалось, и автор просто подумал, что это сделало код более понятным.


Кстати, я думаю, что C должен следовать Suite и создавать логические выражения _Bool, теперь, когда у C есть _Boolи _Generic. Это не должно нарушать большой объем кода, учитывая, что все меньшие типы intв любом случае автоматически продвигаются в большинстве контекстов.
PSkocik

5

Одно простое объяснение состоит в том, что некоторые люди либо не понимают, что условие вернет такое же значение в C, либо думают, что писать проще ((a>b)?1:0).

Это объясняет, почему некоторые также используют подобные конструкции в языках с правильными логическими значениями, которые в C-синтаксисе были бы такими (a>b)?true:false).

Это также объясняет, почему вам не следует менять этот макрос без необходимости.


0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.