Another reason is for the if-else statements. If you use it like this without the do-while(0) in the macro:
if (check)
__set_task_state(tsk, state_value);
else
do_something_else;
The compiler will complain because of a "else" without associated "if" (e.g. orphan "else") as the macro expansion will translate the above code as two instructions under the "if" without braces:
if (check)
(tsk)->state = (state_value);; <-- The double ";;" separates two instructions under the "if".
<-- This makes the following "else" orphan
<-- This requires to add braces to fix the compilation error
else
do_something_else;
The do-while(0) in the macro makes the grouped instructions appear as a single instruction. Here the expansion is:
if (check)
do { (tsk)->state = (state_value); } while(0); <-- Here, there is a single instruction under the "if"
<-- So the following "else" is syntactically correct
<-- No need to add braces
else
do_something_else;
N.B.: This tip is normally used to group several instructions in a macro to make them appear as a single instruction:
#define MACRO() do { inst1; inst2; inst3;...; } while (0)
This makes the following work without the need of braces:
if (check)
MACRO();
else
do_something_else;
This also avoid severe hidden bugs like this one for example:
while(check)
MACRO();
Without the do-while(0) construct, only the first instruction (i.e. inst1) would be executed in the while(check) loop!
In this link, there are additional tips to avoid traps with the c-preprocessor. The above tip is described in §3.