Использование 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)?

5 ответов

Решение

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

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

Выражение !!x, или же !(!x), означает 1, если x является истинным значением (ненулевое число или ненулевой указатель), иначе 0. Это эквивалентно x != 0и это почти так же, как С99 (_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 */
}

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

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

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

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

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

Смотрите также связанный ответ о !!x в JavaScript.

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

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

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

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)));

OP смотрит на какую-то старую идиому кодирования - которая имела смысл BITD (в прошлом).

  1. Основное использование !! должен был обрабатывать реализации 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. В ранних C++ не было bool тип. Так что код, который хотел проверить на достоверность, должен был использовать !! идиома

    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++ сегодня (я знаю, что это вопрос C), когда нет (bool) определенный оператор, не является (int) оператор использовал? Это приводит к той же проблеме, что и № 2. Как и в течение многих ранних лет базы кода C и C++ были синхронизированы, используя !! имел отношение к таким конструкциям, как if (!!x),


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

Другие вопросы по тегам