[英]C macro to assign a value
我有一個形式的結構:
struct foo {
bool has_value;
int value;
}
我想,如下所示創建一個宏,它可以用來分配值:
ASSIGN(foo) = 0;
or
ASSIGN(foo) += 5;
當宏是:
#define ASSIGN(s) \
s->has_value = true; s->value
但是,宏不確保安全。 在下面的示例中, if將“吃掉” ASSIGN
宏的第一部分。
if (cond)
ASSIGN(foo) += 5;
關於如何在安全的情況下進行ASSIGN
任何建議?
使用逗號運算符,並放入括號以進行良好測量。
#define ASSIGN(s) \
((s)->has_value = true), (s)->value
在 if 語句中,它將擴展為:
if (cond)
((foo)->has_value = true), (foo)->value += 5;
但是,這種用法中的宏是一個非常糟糕的主意。
您幾乎總能想出會破壞宏的濫用代碼。
即使它有效,它也會使可能閱讀您代碼的未來程序員感到困惑。
ASSIGN(foo) = M_PI; printf("Sin of PI/2 = %f\\n", sin(ASSIGN(foo) *= 0.5));
您可能希望打印1
。
它甚至可能不會編譯。
關於如何在安全的情況下進行
ASSIGN
任何建議?
一般來說,你不能這樣做。 你不應該使用宏左值。 這是一個糟糕的想法,它很可能會導致瘋狂且不可能找到錯誤。
這看起來真的是一個XY 問題。 在您的情況下,據我所知,您想要創建一個宏來簡化應該在代碼中多次重復執行的表達式。
您可以定義一個帶有兩個參數的宏,而不是使用簡單的單參數宏。 這樣,它將與您的其余代碼更加兼容,同時仍然實現相同的結果,使用更一致的語義,這也是“if-safe”。
這是(我稱它為ASSIGN_FOO
只是為了將它與您的區別開來):
#define ASSIGN_FOO(x, v) do { (x)->has_value = true; (x)->value = v; } while (0)
struct foo var;
ASSIGN_FOO(var, 123);
如果您想知道do { ... } while (0)
,請看這里。
如果您希望宏返回分配的值(就像您對正常分配所期望的那樣),這不是一個好的選擇。
您可以定義一個內聯函數,而不是使用宏,用__attribute__ ((always_inline))
聲明它。 這樣,編譯器將函數的代碼直接集成到調用者中,該函數在編譯程序時將完全充當宏,只是現在功能更強大,因為它可以在更多上下文中使用。
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);
除此之外,在更新結構中的值時使用您定義的宏沒有多大意義,因為它很容易導致不需要的未定義行為,如下所示:
struct foo var;
ASSIGN(var) += 5;
其中擴展為:
var->has_value = true; var->value += 5; // Undefined behavior, var->value used uninitialized!
這里的解決方案是:
如果您已經知道該值存在,則重新分配has_value = true
沒有意義,您可以直接進行增量:
var->value += 10;
如果您不知道該值是否存在,請使用函數來安全地執行此操作:
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);
比較:
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.
這種破壞語法的宏通常是一個壞主意,但您可以使用
#define ASSIGN(s) ((s).has_value=1,&(s))->value
如果您希望它采用左值或
#define ASSIGN(sp) ((sp)->has_value=1,(sp))->value
如果你想讓它帶一個指針。
工作示例( https://godbolt.org/z/fHAGRa ):
#include <stdbool.h>
#include <stdio.h>
struct foo {
bool has_value;
int value;
};
#define ASSIGN(s) ((s).has_value=1,&(s))->value
int main()
{
struct foo x;
ASSIGN(x) = 21;
printf("%d\n", ASSIGN(x) *= 2 ); //prints 42
return ASSIGN(x); //returns the last value (42)
}
它不能防止雙重評估——為此您需要表達式語句擴展或輔助內聯函數,例如
static inline struct foo *set_has_value_return_ptr(struct foo *X){
return X->has_value=1, X;
}
#define ASSIGN(s) (set_has_value_return_ptr(&(s)))->value
解釋:
天真的((s).has_value=1,(s).value)
或((s).has_value=1,(s)).value
不起作用,因為逗號運算符完成了左值到右值的轉換,但是((s).has_value=1,&(s))->value
or (*((s).has_value=1,&(s))).value
會因為指針解引用 (*) 和->
總是產生左值不管它們的指針參數是左值還是右值。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.