【问题标题】:Using if (!!(expr)) instead of if (expr)使用 if (!!(expr)) 代替 if (expr)
【发布时间】:2016-02-20 12:08:04
【问题描述】:

在阅读德州仪器为其SensorTag 提供的示例代码时,我遇到了以下 sn-p。

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) 有什么优势吗?

【问题讨论】:

  • @CoolGuy:那不是骗局。链接问题中的参数转换为布尔值是有原因的。在这里它是无用的,因为值本身没有被处理。
  • @Olaf 不,__builtin_expect(x, 0)__builtin_expect(!!(x),0) 应该表现相同,因此在那里也没用
  • 当然,到目前为止,这里提供的答案都没有提供任何关于为什么 if (!!((ioValue & IO_DATA_BUZZER))) { 会在按位与周围包含 double 括号的任何见解。这样的事情让我想知道我们是否只是看到遭受剪切和粘贴/搜索和替换损坏的代码。

标签: c expression


【解决方案1】:

两次应用逻辑非 (!) 运算符的目的是将值标准化为 0 或 1。在 if 语句的控制表达式中,这没有任何区别。 if 语句只关心值是零还是非零,小!! 舞蹈完全没用。

一些编码风格指南可能会要求这种舞蹈,这可能是您发布的 TI 代码这样做的原因。不过,我还没有看到任何这样做的。

【讨论】:

  • 同意。这在嵌入式编程中也非常少见。我怀疑这是展开的一些遗物,例如一个BITTEST 宏,它应该返回一个位的值,而不仅仅是用于进一步处理的设置/清除信息。不是这样的代码通常是由学生等编写的,他们本身不是很有经验(而且报酬也不高),所以他们倾向于使用现有的模式并且没有很好地优化/增强。
  • @GOTO0:这可能使他们经验丰富的 Java 开发人员,而不是经验丰富的 C 开发人员
  • @Olaf 由于 Java 是一种强类型语言,除了布尔值之外,没有其他操作数类型可以应用逻辑否定,这使得双重否定毫无用处。我能想到的唯一可能的原因可能是在应用于盒装布尔值时强制 NPE。
  • 一些编译器警告像if (a = b) 这样的结构,因为这些很可能是错误的。您可以使用if ((a = b) != 0) 来避免警告,但这很丑陋。 if (!!(a = b))有点更简洁。
  • @EOF 通常,编译器会选择if ((a = b))。我认为这个约定可以追溯到 lint。
【解决方案2】:

表达式!!x,或!(!x),如果x为真值(非零数或非空指针)则为1,否则为0。它等价于x != 0,几乎相同与 C99 (_Bool)x 相同,但在 C99 之前的编译器或开发人员选择不实现 C99 的编译器中可用(例如针对 MOS 6502 的 cc65)。

整个条件等价于:

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 Warning Options 描述如下诊断:

-Wlogical-op:警告表达式中逻辑运算符的可疑用途。这包括在可能需要按位运算符的上下文中使用逻辑运算符。

-Wparentheses:在某些上下文中省略括号时发出警告,例如在预期为真值的上下文中进行赋值时

使用(a & B) != 0(_Bool)(a & B)!!(a & B) 等释义会向编译器和其他开发人员传达有意使用位运算符的信息。

另请参阅有关!!x in JavaScript 的相关答案。

【讨论】:

  • 同意。我立即查看了 OP 的代码并看到了位掩码 - !! 真的很清楚。
  • 谢谢!为了解释。
  • 这是一个比接受的答案更好的答案,它没有解决按位运算符方面。
  • 注意“它 (!!x) 等效于 x != 0,或 C99 (_Bool)x” --> 类型不同,可能大小不同。
  • 这似乎是一个比接受的更好的答案。
【解决方案3】:

在 MSVC 中,在 if 语句中将整数隐式转换为 bool 会产生警告。通过!! 这样做不会。其他编译器中可能存在类似警告。

因此,假设代码是在启用该警告的情况下编译的,并且决定将所有警告视为错误,使用 !! 是一种简短且可移植的方式来表示“是的,我希望这个整数成为 bool” .

【讨论】:

  • 但是...if 语句的控制表达式不会被隐式转换。也许您将 C 与 C++ 混淆了?
【解决方案4】:

虽然最有可能消除按位 & 的编译器警告,但这看起来也可能是重构以添加枚举以提高可读性的结果:

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

【讨论】:

  • 您没有明确提及,但!! 出现在PIN_setOutputValue(hGpioPin, Board_LED1,!!(ioValue &amp; IO_DATA_LED1)); 中的原因是该函数将被声明为采用某种整数类型参数(可能是int 或@例如 987654330@),但是如果标志的值很大,那么即使测试成功,结果也可能转换为0
  • @M.M 好点,我想这并不明显。我只是习惯了typedef char bool;的平台,所以没想到要提这个
【解决方案5】:

OP 正在研究一些旧的编码习语 - 这在 BITD (过去)有些意义。

  1. !! 的主要用途是处理将 if(expr) 中的表达式转换为 int 的 C 实现,而不是针对零进行测试。

考虑将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. early C++ 中,没有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. 当没有定义 (bool) 运算符时,今天的 C++ 会发生什么(我知道这是一个 C 问题),是不是使用了 (int) 运算符?这导致与#2相同的问题。早几年 C 和 C++ 代码库保持同步,使用 !!if (!!x) 等结构相关。


现在使用 !! 有效,但肯定已经失宠,因为它解决了不再频繁发生的问题。

【讨论】:

  • !! 在将测试结果传递给函数时仍用于符合 C89
  • @M.M 同意,这是!! 的有用应用。不过,这篇文章的重点是与 if() 一起使用。
猜你喜欢
  • 1970-01-01
  • 2019-02-07
  • 1970-01-01
  • 2020-12-18
  • 1970-01-01
  • 1970-01-01
  • 2017-06-24
  • 2019-04-11
  • 1970-01-01
相关资源
最近更新 更多