While reading the example code provided by Texas Instruments for their SensorTag I came across the following snippet.
void SensorTagIO_processCharChangeEvt(uint8
OP is looking at some old coding idiom - which made some sense BITD (back-in-the day).
!!
was to handle C implementations that converted the expression in an if(expr)
to int
rather than testing against zero.Consider what happens when expr
is converted to int
and then tested against 0. (Since C89, this is non-conforming as the test should be a direct test against 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)
So on pre C89 compliers and non-conforming C89 and later, using !!
coped with that weakness. Some old habits take a long time to die.
In early C++, there was no bool
type. So code that wanted to test for trustfulness needed to use the !!
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)
What happens with C++ today (I know this is a C question) when there is no (bool)
operator defined, is not the (int)
operator used? This results in the same problem as #2. As for many early years C and C++ code bases were kept in sync, using !!
had relevance with constructs like if (!!x)
.
Using !!
works today but has certainly fallen out of favor as it solves problem that no longer occurs with any significant frequency.
In MSVC converting an integer to a bool
implicitly in an if
statement can generate a warning. Doing so via !!
does not. Similar warning may exist in other compilers.
So supposing the code was compiled with that warning enabled, and a decision to treat all warning as errors, using !!
is a short and portable way to say "yes, I want this integer to be a bool
".
Although silencing the compiler warning for the bit-wise &
is the most likely, this looks like it could also be the result of a refactoring to add enums for readability from:
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
to:
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);
Since that exceeded the 80 character limit it was then refactored to
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);
}
Personally I would have preferred the initial version for readability, but this version is common when lines of code are used as a metric (I'm surprised it didn't declare variables for each state, set each state separately and then use that).
The next version of this "Best Practice" code might look like:
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
All of that could have been done like this:
for (int i=0;i<numleds;i++)
PIN_setOutputValue(hGpioPin, i ,!!(ioValue & (1<<i)));
The expression !!x
, or !(!x)
, means 1 if x
is a true value (a nonzero number or a nonnull pointer), otherwise 0. It is equivalent to x != 0
, and it's nearly the same as C99 (_Bool)x
but available in compilers that predate C99 or whose developers have chosen not to implement C99 (such as cc65 which targets the MOS 6502).
The conditional as a whole is equivalent to the following:
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 */
}
In C, it means "if the bitwise AND of those two values is nonzero, execute the block."
But some coding style guides might prohibit a bitwise AND (&
) at the top level of an if
statement's condition, assuming it to be a typo for the logical AND (&&
). It's in the same class of mistakes as using =
(assignment) instead of ==
(equality comparison), for which many compilers offer a diagnostic. GCC Warning Options describes diagnostics like these:
-Wlogical-op
: Warn about suspicious uses of logical operators in expressions. This includes using logical operators in contexts where a bit-wise operator is likely to be expected.
-Wparentheses
: Warn if parentheses are omitted in certain contexts, such as when there is an assignment in a context where a truth value is expected
Use of a paraphrase such as (a & B) != 0
, (_Bool)(a & B)
, or !!(a & B)
communicates to the compiler and to other developers that use of a bitwise operator was intentional.
See also a related answer about !!x in JavaScript.
Applying the logical not (!
) operator twice has the purpose of normalising a value to be either 0 or 1. In a control expression of an if-statement, that doesn't make any difference. The if-statement only cares about the value being zero or nonzero, the little !!
dance is completely useless.
Some coding style guides might mandate this kind of dance, which could be the reason why the TI code you posted does it. I haven't seen any that do so though.