簡體   English   中英

定義宏時使用逗號運算符

[英]Using the comma operator when defining a macro

考慮到逗號運算符將適合使用K&R狀態的情況(第3章,第63頁)“ ...,以及在多步計算必須為單個表達式的宏中。

現在,我知道#define SYMVAR (expr1, expr2, expr3)SYMVAR設置為expr3但這並不是本書所建議的用途。 我的困惑源於缺少使用逗號運算符的上述示例; 其次,只有當每個逗號分隔的表達式實際上以任何方式對宏本身的值有所貢獻時,宏中的多步計算才是有目的的,只有當我們以某種方式將一些中間值存儲在某些臨時變量中而不會任何意義,因為我們在談論預處理器指令。

我想念什么嗎? 本書描述的使用逗號運算符的正確示例是什么?

想象一個功能

void debug_log(const char *s);

將指定的字符串(連同時間戳)寫入某種日志文件。

然后,您可以定義以下宏:

#define TWICE(X)  (debug_log("Calling TWICE(" #X ")"), (X) * 2)

然后, TWICE(21)計算結果為42 ,但還會寫出以下形式的消息

[2019-09-03 12:34:56] Calling TWICE(21)

到日志文件。

關鍵是C語言中的表達式可能會有副作用。 即使不使用返回值,他們也可以修改變量,可以寫入文件等。

甲腎上腺素提供了一個例子。 另一個示例,盡管您可能會爭論它是好是壞,但是是否要在循環頭中使用該宏。

#define MACRO(X) (X--, X>0)

int x=5;
while(MACRO(x)) {
    // Do stuff
}

此示例絕對不是世界上最好的示例,但要點是,如果使用分號代替表達式,無論如何封裝它們,它都將無法工作。

由於逗號運算符語句的值是語句中最后一個逗號分隔的表達式,因此您可以使用它執行多個表達式,同時僅保留最后一個表達式的值。

例如,假設您希望有一個宏來執行多個計算,但是該宏的值是最后一個計算。 以下宏利用了表達式中使用的賦值運算符來執行計算,並允許將不同的計算作為宏的結果:

#define SYMVAR(a,b)  ( ((a)=(b)), (b)*2)

這也可以是一系列函數調用,例如:

int f1(int x) { return x * 3; }

int f2(int x) { return x * 4; }

// call first function f1() and then f2() with the value of the macro being the
// value returned by function f2().
#define SYMVARy(a) (f1(a), f2(a))

使用此方法的示例:

#include <stdio.h>

#define SYMVAR(a,b)  ( ((a)=(b)), (b)*2)

#define SYMVAR2(a,b,z)  ( ((a)=(b)), ((z)=(b))*2)

int main()
{
    int n1, n2;
    int x1, x2;
    int y0;
    int i;

    for (i = 0; i < 10; i++) {
        printf(" i = %d  -> ", i);
        x1 = SYMVAR(n1, i);
        printf(" n1 = %d, i = %d, x1 = %d\n", n1, i, x1);
    }

    printf("\nloop 2\n");
    for (i = 0; i < 10; i++) {
        printf(" i = %d  -> ", i);
        x2 = SYMVAR2(n2, i+3, y0);
        printf(" n2 = %d, i = %d, x2 = %d, y0 = %d\n", n2, i, x2, y0);
    }

    return 0;
}

該程序的輸出為:

 i = 0  ->  n1 = 0, i = 0, x1 = 0
 i = 1  ->  n1 = 1, i = 1, x1 = 2
 i = 2  ->  n1 = 2, i = 2, x1 = 4
 i = 3  ->  n1 = 3, i = 3, x1 = 6
 i = 4  ->  n1 = 4, i = 4, x1 = 8
 i = 5  ->  n1 = 5, i = 5, x1 = 10
 i = 6  ->  n1 = 6, i = 6, x1 = 12
 i = 7  ->  n1 = 7, i = 7, x1 = 14
 i = 8  ->  n1 = 8, i = 8, x1 = 16
 i = 9  ->  n1 = 9, i = 9, x1 = 18

loop 2
 i = 0  ->  n2 = 3, i = 0, x2 = 6, y0 = 3
 i = 1  ->  n2 = 4, i = 1, x2 = 8, y0 = 4
 i = 2  ->  n2 = 5, i = 2, x2 = 10, y0 = 5
 i = 3  ->  n2 = 6, i = 3, x2 = 12, y0 = 6
 i = 4  ->  n2 = 7, i = 4, x2 = 14, y0 = 7
 i = 5  ->  n2 = 8, i = 5, x2 = 16, y0 = 8
 i = 6  ->  n2 = 9, i = 6, x2 = 18, y0 = 9
 i = 7  ->  n2 = 10, i = 7, x2 = 20, y0 = 10
 i = 8  ->  n2 = 11, i = 8, x2 = 22, y0 = 11
 i = 9  ->  n2 = 12, i = 9, x2 = 24, y0 = 12

這種方法的局限性

這種方法的局限性是無法創建臨時變量,其范圍是逗號表達式本身。

解決方法是使用do { … } while(0)構造,但是這樣您就會遇到宏在賦值中不再有效的問題。

其他一些注意事項

這種東西使用起來可能有些棘手。 您必須始終記住,C預處理程序正在執行文本替換,並且對此沒有很多技巧。

您應該使用括號強制執行特定的求值順序,並確保使用宏時提供的替換文本將導致正確的運算順序( (i + 3) * 2而不是i + 3 * 2 ,這是兩個由於運算符的優先順序而不同的語句)。

還應注意,在一個宏指令中多次使用宏參數使用具有副作用的表達式可能會導致意想不到的后果。 例如:

#define SYMVAR(a,b)  ( ((a)=(b)), (b)*2)

int xA, xB = 2, xVal;

xVal = SYMVAR(xA, xB++);    // macro argument has a side effect, post increment

將導致xVal的值為6( xB遞增一次,因為后遞增,然后乘以2), xA的值為2(由於使用post遞增而原始的xB值), xB的值為4( xB為遞增兩次)。

您可以在類似以下內容中使用上述宏之一:

xx SYMVAR(n1, n2);

它會編譯一個警告,然后在鏈接期間失敗。 在Visual Studio 2017中,將上述語句添加到上述程序中時會看到以下警告和錯誤。 請注意,這些警告和錯誤都是與缺少外部函數xx ,而不是您可能期望的編譯器錯誤。

Severity    Code    Description Project File    Line    Suppression State
Warning C4013   'xx' undefined; assuming extern returning int   console_scan    d:\users\rickc\documents\vs2017repos\console_scan\console_scan\source_macro.c   27  
Error   LNK2019 unresolved external symbol _xx referenced in function _main console_scan    D:\Users\rickc\Documents\vs2017repos\console_scan\console_scan\Source_Macro.obj 1   
Error   LNK1120 1 unresolved externals  console_scan    D:\Users\rickc\Documents\vs2017repos\console_scan\Debug\console_scan.exe    1   

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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