Чому тернарний оператор використовується для визначення 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
Ні, причин немає. Ти правий.
Арт

29
Частково не в темі: Коли божевілля від використання препроцесора припиняється? Тут існує потенційна багаторазова оцінка функцій. Просто непотрібне.
stefan

3
Іноді теж приємно бути явним. Тут потрійний оператор з першого погляду дає зрозуміти, що метою макросу є повернення логічного значення.
труба

5
Принаймні, макроси слід використовувати (alpha_char)замість alpha_char, лише для того, щоб переконатися, що вони не зламаються, якщо хтось спробує щось шаленеATCI_IS_LOWER(true || -1) .
Джастін Тайм - відновити Моніку

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

Відповіді:


131

Ви праві, в С це тавтологічно. Як ваш конкретний трійковий умовний, так і (1 > 0) тип int.

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

Я здогадуюсь, що автор задумався над цим, намагаючись зберегти сумісність C ++.


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

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

3
чи не було б більш очевидним розглядати цю справу, відкладаючи результат, intа не за допомогою трійки?
мартінкунев

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

Це найкраща причина, яку я чув, щоб не ставити пробіли в дужках у програмі 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 повинен слідувати за набором і робити логічні вирази _Bool, тепер, коли у C є _Boolі _Generic. Це не повинно порушувати багато коду, враховуючи, що всі менші типи intвсе одно автопромотуються в більшості контекстів.
PSkocik

5

Одне з простих пояснень полягає в тому, що деякі люди або не розуміють, що умова поверне одне і те ж значення в С, або вони вважають, що це чистіше писати ((a>b)?1:0).

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

Це також пояснює, чому вам не слід без потреби змінювати цей макрос.


0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.