簡體   English   中英

C宏賦值

[英]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!

這里的解決方案是:

  1. 如果您已經知道該值存在,則重新分配has_value = true沒有意義,您可以直接進行增量:

     var->value += 10;
  2. 如果您不知道該值是否存在,請使用函數來安全地執行此操作:

     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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM