Используя if (!! (expr)) вместо if (expr)

Прочитав пример кода, предоставленного Texas Instruments для SensorTag, я наткнулся на следующий фрагмент.

void SensorTagIO_processCharChangeEvt(uint8_t paramID) { 
    ...

    if (!!(ioValue & IO_DATA_LED1)) {
        PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_ON);
    } else {
        PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_OFF);
    }

    if (!!(ioValue & IO_DATA_LED2)) {
        PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_ON);
    } else {
        PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_OFF);
    }

    if (!!((ioValue & IO_DATA_BUZZER))) {
        Clock_start(buzzClockHandle);
    }
    ...
}

Объявление выглядит так (в том же файле).

#define IO_DATA_LED1   0x01
static uint8_t ioValue;

Предоставляет ли if (!!(ioValue & IO_DATA_LED1)) преимущество перед if (ioValue & IO_DATA_LED1)?

Ответ 1

Применять логический не (!) оператор дважды имеет целью нормализовать значение, равное 0 или 1. В контрольном выражении if-statement это не имеет никакого значения. Операция if только заботится о том, чтобы значение было нулевым или ненулевым, маленький танец !! полностью бесполезен.

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

Ответ 2

Выражение !!x или !(!x) означает 1, если x - истинное значение (ненулевое число или ненулевой указатель), в противном случае 0. Это эквивалентно x != 0, и это почти то же самое как C99 (_Bool)x, но доступный в компиляторах, которые предшествовали C99 или разработчики решили не использовать C99 (например, cc65, который нацелен на MOS 6502).

Условие в целом эквивалентно следующему:

if (ioValue & IO_DATA_LED1) {
    /* what to do if the IO_DATA_LED1 bit is true */
} else {
    /* what to do if the IO_DATA_LED1 bit is false */
}

В C это означает "если побитовое И этих двух значений отличное от нуля, выполните блок".

Но некоторые руководства по стилю кодирования могут запрещать побитовое И (&) на верхнем уровне условия оператора if, предполагая, что это опечатка для логического И (&&). Это в том же классе ошибок, что и использование = (присвоение) вместо == (сравнение равенства), для которого многие компиляторы предлагают диагностику. Параметры предупреждения GCC описывает такие диагностические операции:

-Wlogical-op: Предупреждать о подозрительном использовании логических операторов в выражениях. Это включает использование логических операторов в контекстах, где вероятный оператор может ожидать.

-Wparentheses: Предупреждать, если круглые скобки опущены в определенных контекстах, например, когда есть назначение в контексте, где ожидается истинное значение

Использование парафраза, такого как (a & B) != 0, (_Bool)(a & B) или !!(a & B), связывается с компилятором и с другими разработчиками, которые используют побитовый оператор, были намеренными.

См. также соответствующий ответ о !!x в JavaScript.

Ответ 3

В MSVC преобразование целого числа в bool неявно в операторе if может генерировать предупреждение. Делать это через !! нет. Подобное предупреждение может существовать и в других компиляторах.

Предположим, что код был скомпилирован с включенным предупреждением, и решение обрабатывать все предупреждения как ошибки, используя !! - это короткий и переносимый способ сказать "да, я хочу, чтобы это целое число было bool",.

Ответ 4

OP смотрит на какую-то старую икону кодирования, которая имела некоторый смысл BITD (назад в день).

  • Первичное использование !! состояло в том, чтобы обрабатывать реализации C, которые преобразовывали выражение в if(expr) в int, а не тестирование против нуля.

Рассмотрим, что происходит, когда expr преобразуется в int, а затем проверяется на 0. (Поскольку C89, это несоответствие, поскольку тест должен быть прямым тестом против 0)

int i;
long li;
double d;

// no problems
if (i & 5) ...
if (d > 4.0) ...

// problems
if (li & 0x10000) ...  (Hint: int is 16-bit)
if (d)                 (d might have a value outside `int` range.

// fix
if (!!(li & 0x10000))
if (!!d)

Итак, на предварительных C89-компиляторах и несоответствующих C89 и более поздних версиях, используя !! справился с этой слабостью. Некоторым старым привычкам требуется много времени, чтобы умереть.

  1. В раннем С++ не было типа bool. Таким образом, код, который хотел проверить на достоверность, должен был использовать !! idiom

    class uint256;  // Very wide integer
    uint256 x;
    
    // problem  as (int)x may return just the lower bits of x
    if (x) 
    
    // fix
    if (!!x) 
    
  2. Что происходит с С++ сегодня (я знаю, что это вопрос C), когда нет оператора (bool), не используется ли оператор (int)? Это приводит к той же проблеме, что и # 2. Что касается многих ранних лет, базы кода C и С++ поддерживались синхронно, использование !! имело отношение к конструкциям типа if (!!x).


Использование !! работает сегодня, но, безусловно, не понравилось, поскольку оно решает проблему, которая больше не возникает с какой-либо значительной частотой.

Ответ 5

Хотя молчание предупреждения компилятора для бит-мута & наиболее вероятно, похоже, что это также может быть результатом рефакторинга для добавления перечислений для удобочитаемости из:

PIN_setOutputValue(int,int,bool); //function definition
PIN_setOutputValue(hGpioPin, Board_LED1,!!(ioValue & IO_DATA_LED1));
PIN_setOutputValue(hGpioPin, Board_LED2,!!(ioValue & IO_DATA_LED2));
//note: the !! is necessary here in case sizeof ioValue > sizeof bool
//otherwise it may only catch the 1st 8 LED statuses as @M.M points out

в

enum led_enum {
  Board_LED_OFF = false,
  Board_LED_ON = true
};
PIN_setOutputValue(int,int,bool); //function definition
//...
PIN_setOutputValue(hGpioPin, Board_LED1,!!(ioValue & IO_DATA_LED1)?Board_LED_ON:Board_LED_OFF);
PIN_setOutputValue(hGpioPin, Board_LED2,!!(ioValue & IO_DATA_LED2)?Board_LED_ON:Board_LED_OFF);

Так как это превысило ограничение на 80 символов, оно было реорганизовано на

if (!!(ioValue & IO_DATA_LED1)) {
    PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_ON);
} else {
    PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_OFF);
}

if (!!(ioValue & IO_DATA_LED2)) {
    PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_ON);
} else {
    PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_OFF);
}

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

Следующая версия этого кода "Best Practice" может выглядеть так:

bool boardled1State;
bool boardled2State;
//...

boardled1State = !!(ioValue & IO_DATA_LED1);
boardled2State = !!(ioValue & IO_DATA_LED2);
//...

if (boardled1State) {
    PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_ON);
} else {
    PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_OFF);
}

if (boardled2State) {
    PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_ON);
} else {
    PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_OFF);
}
//... and so on

Все это можно было бы сделать следующим образом:

for (int i=0;i<numleds;i++)
        PIN_setOutputValue(hGpioPin, i ,!!(ioValue & (1<<i)));