web-dev-qa-db-ja.com

if(expr)の代わりにif(!!(expr))を使用する

SensorTag についてTexas Instrumentsが提供するサンプルコードを読んでいると、次のスニペットに出会いました。

_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)よりも利点がありますか?

66
Mohammad Yaseen

論理否定(!)演算子を2回使用する目的は、値を0または1に正規化することです。ifステートメントの制御式では、違いはありません。 ifステートメントは、値がゼロまたは非ゼロであることのみを考慮し、小さな!!ダンスはまったく役に立たない。

一部のコーディングスタイルガイドでは、この種のダンスが義務付けられている場合があります。私はそうすることを見たことがありません。

79
fuz

!!xまたは!(!x)は、xが真の値(非ゼロの数値または非NULLポインター)の場合は1、それ以外の場合は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では、「これら2つの値のビット単位のANDがゼロ以外の場合、ブロックを実行する」という意味です。

ただし、一部のコーディングスタイルガイドでは、論理AND(&)の入力ミスであると想定して、ifステートメントの条件の最上位でビット単位のAND(&&)を禁止している場合があります。多くのコンパイラーが診断を提供する=(等値比較)の代わりに==(割り当て)を使用するのと同じクラスの間違いです。 GCC警告オプション は、次のような診断を説明します。

-Wlogical-op:式での論理演算子の疑わしい使用について警告します。これには、ビット単位の演算子が予想されるコンテキストでの論理演算子の使用が含まれます。

-Wparentheses:真理値が期待されるコンテキストに割り当てがある場合など、特定のコンテキストで括弧が省略されている場合に警告します

(a & B) != 0(_Bool)(a & B)、または!!(a & B)などの言い換えの使用は、コンパイラーと、ビット演算子の使用が意図的な他の開発者と通信します。

JavaScriptの!!x に関する関連する回答も参照してください。

54
Damian Yerrick

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

個人的には読みやすさのために初期バージョンを好んでいましたが、このバージョンはコード行がメトリックとして使用される場合に一般的です(各状態の変数を宣言せず、各状態を個別に設定してからそれを使用します)。

この「ベストプラクティス」コードの次のバージョンは次のようになります。

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

OPは古いコーディングのイディオムを検討しています-これはBITD(当時)の意味を成していました。

  1. _!!_の主な用途は、ゼロに対してテストするのではなく、if(expr)の式をintに変換するC実装を処理することでした。

exprintに変換され、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. _(bool)_演算子が定義されておらず、_(int)_演算子が使用されていない場合、今日のC++で何が起こりますか(これはCの質問です)。これは、#2と同じ問題になります。初期の多くの場合、CおよびC++コードベースの同期が保たれていたため、_!!_の使用はif (!!x)のような構造と関連性がありました。


_!!_を使用することは今日でも機能しますが、重要な頻度では発生しなくなった問題を解決するため、確かに支持を失いました。

3
chux