#define 的神奇操作

MElephant發表於2022-12-04

# define 的神奇操作

一、宏定義中的 #、## 符號的神奇用法

1.1 # 的用法

1.1.1 作用

#表示字串化運算子(stringification),其作用是將宏定義中的傳入引數名轉換成用雙引號括起來的引數名字串

現在對這句話是不是還不甚理解,沒關係,讓我們接著往下走。

1.1.2 舉例說明

#include <stdio.h>

#define ToString(str) #str

int main()
{
    char str[] = ToString(hello  world); // 等價於 "hello world";
}

根據例子,我們再來理解一下#的作用:將宏定義(ToString)中的傳入引數名(hello world)轉化成用雙引號括起來的引數名字串("hello world")。

是不是有種恍然大明白的感覺~

1.1.3 實戰

假設給你一個 JSON 字串:

{"name" : "張三"}

如何以 C 語言的形式輸出呢?

你可以這麼做char str[] = "{\"name\":\"張三\"}";,透過使用跳脫字元\"使得程式碼可以儲存引號"

既然學習了宏定義中#的作用,那麼我們透過#修改一下:

#define ToString(str)   #str
int main()
{
    char str[] = ToString({"name":"張三"});
}

好處就是不用新增\,閱讀起來更直觀。

1.2 ## 的用法

1.2.1 作用

##表示連線,將宏定義中的一個或多個形參轉換成一個實際的引數名

1.2.2 舉例說明

老規矩,先上程式碼再解釋:

#include <stdio.h>

#define Conn(x, y) x##y

int main()
{
    int num1 = 10;
    int n = Conn(num, 1); // n = num1

    return 0;
}

根據例子,我們再來理解一下##的作用:將宏定義(Conn)中的多個形參(num 和 1)轉換成一個實際的引數名(num1)。

或者你也可以這麼寫:

#include <stdio.h>

#define Conn(x) num##x

int main()
{
    int num1 = 10;
    int n = Conn(1); // n = num1

    return 0;
}

將宏定義(Conn)中的一個形參(1)轉化為實際的引數名(num1)。

1.2.3 錯誤用法

透過上面的舉例,你現在是不是已經明白了##的作用了,那麼來分析一下下述程式碼的輸出結果:

#include <stdio.h>

#define Conn(x) num##x

int main()
{
    int num1 = 1;
    int num2 = 2;
    int num3 = 3;
    int numi = 100;

    for (int i = 1; i <= 3; i++)
    {
        int num = Conn(i);
        printf("num = %d\n", num);
    }

    return 0;
}

期望的輸出結果是不是:

  • num = 1
  • num = 2
  • num = 3

下面讓我們看一下實際執行結果:
image-20221204144244309

是不是很意外?Conn 宏不是起連線作用嗎為什麼輸出的結果不是預期呢?

還記得 gcc 的 -E 指令嗎,讓我們透過 -E 看一下 gcc 預編譯的檔案:
image-20221204144449013

下面讓我們來分析一下 main.i 檔案內容:

int main()
{
    int num1 = 1;
    int num2 = 2;
    int num3 = 3;
    int numi = 100;

    for (int i = 1; i <= 3; i++)
    {
        int num = numi;
        printf("num = %d\n", num);
    }

    return 0;
}

是不是找到問題所在了。原因在於Conn(i)並沒有按照我們的預期依次替換為 num1、num2、num3,而是替換為了 numi。

將宏 Conn(i) 中 i 的值替換為其實際的整數值是不可能的,這是因為宏替換髮生在程式碼編譯之前,也就是說宏替換的時候並不知道 i 是一個 int 型的變數,僅僅將其當做一個字元處理。

解釋參考自問題評論:將變數的值傳遞給C中的宏 - 或程式碼 (orcode.com)

二、實際使用

最近在看 ONVIF 程式碼的時候,看到一個神奇的操作,下面是簡化版本:

#include <stdio.h>

/* 函式宣告 */
#define DECLARE(x) \
    void Func_##x(int a);

/* 函式定義 */
#define DEFINE(x) \
    void Func_##x(int a) \
    { \
        printf("a 的值為 %d, %s 型\n", a, #x); \
    }

/* 函式建立 */
#define CREATE(x) \
    Func_##x

DECLARE(int)
DEFINE(int)

int main()
{
    CREATE(int)(10);

    return 0;
}

在學習了###的用法後,上面的程式碼就迎刃而解了,轉化成普通的程式碼為:

#include <stdio.h>

void Func_int(int a);       // 宣告函式 --- DECLARE(int)
void Func_int(int a)        // 定義函式 --- DEFINE(int)
{
    printf("a 的值為 %d, %s 型\n", a, "int");
}

int main()
{
    Func_int(10);           // 使用函式 --- CREATE(int)(10)

    return 0;
}

參考資料

相關文章