Any proposal on how to make ASSIGN
if safe?
You cannot, in general, do this. You should not use macros for an lvalue. It's a terrible idea, and it will most probably lead to crazy and impossible to find bugs.
This really seems like an XY problem. In your case, as I understand, you want to create a macro to simplify an expression that should be done over and over multiple times in your code.
Instead of using a simple one-argument macro, you can define a macro with two arguments. This way, it will be more compatible with the rest of your code, while still achieving the same result, using a more consistent semantic, which is also "if-safe".
Here it is (I'm calling it ASSIGN_FOO
just to distinguish it from yours):
#define ASSIGN_FOO(x, v) do { (x)->has_value = true; (x)->value = v; } while (0)
struct foo var;
ASSIGN_FOO(var, 123);
If you're wondering about that do { ... } while (0)
, have a look here.
In case you want the macro to return the assigned value though (like you would expect from a normal assignment), this is not a good option.
Instead of using a macro, you can define an inline function, declaring it with __attribute__ ((always_inline))
. This way, the compiler integrates the function's code directly into the caller, and the function will act exactly as a macro when the program is compiled, except that it is now more powerful as it can be used in more contexts.
inline int __attribute__ ((always_inline)) assign_foo(struct foo *x, int value) {
x->has_value = true;
x->value = value;
return x->value;
}
struct foo var;
assign_foo(var, 123);
In addition to this, it doesn't make much sense to use the macro you defined when updating the value in your struct, as it can easily lead to unwanted undefined behavior, like the following:
struct foo var;
ASSIGN(var) += 5;
Which expands to:
var->has_value = true; var->value += 5; // Undefined behavior, var->value used uninitialized!
The solution here is:
If you already know that the value is present, it doesn't make sense to re-assign has_value = true
, you can just do the increment directly:
var->value += 10;
If you don't know if the value is present, use a function to do it safely instead:
inline int __attribute__ ((always_inline)) increment_foo(struct foo *x, int value) {
if (!x->has_value) {
x->has_value = true;
x->value = value;
} else {
x->value += value;
}
return x->value;
}
increment_foo(var, 10);
Comparison:
struct foo x;
printf("%d\n", ASSIGN(x) = 3); // Compilation error.
printf("%d\n", ASSIGN_FOO(x, 3); // Compilation error.
printf("%d\n", assign_foo(x, 3)); // No problem.
struct foo y;
printf("%d\n", ASSIGN(y) += 3); // Compilation error.
ASSIGN(y) += 3; printf("%d\n", y->value); // Undefined behavior.
printf("%d\n", increment_foo(y, 3)); // No problem.