首頁 > 軟體

C語言中帶返回值的宏定義方式

2023-02-25 06:00:50

C語言中帶返回值的宏定義

相信大家在實際工作中,一定有遇到需要編寫一個宏定義,且希望它能帶返回值的場景吧?

比如我之前就遇到一個場景,早期的程式碼是使用函數實現的功能,現在想換成宏定義,但是又要保留之前呼叫函數的程式碼不動,這樣我就只能想辦法寫一個帶返回值的宏了。

宏定義編寫

直接上demo:

#include <stdio.h>

/* always return 1 */
#define RETURN_MACRO()            ({do {} while(0);1;})
#define RETURN_MACRO2()            1

/* return a+b */
#define A_PLUS_B_MACRO(a, b)    ({int ret; ret = (a) + (b); ret;})
#define A_PLUS_B_MACRO2(a, b)    ({int ret; ret = add((a), (b)); ret;})

int add(int a, int b)
{
    return (a + b);
}

int main(int argc, const char *argv[])
{
    int a = 6;
    int b = 7;

    printf("Hello world !n");
    printf("RETURN_MACRO: %dn", RETURN_MACRO());
    printf("RETURN_MACRO2: %dn", RETURN_MACRO2());
    printf("a + b = %dn", A_PLUS_B_MACRO(a, b));
    printf("a + b = %dn", A_PLUS_B_MACRO2(a, b));

    return 0;
}

宏定義分析

為了分析宏定義的寫法,我們得知道宏定義最終被展開是什麼樣的。

我在之前的博文中有提到,使用gcc編譯器的話,可以在CFLAGS上加上-save-temps=obj這個編譯選項,這樣就可以得到預編譯處理之後的檔案,字尾名是.i。

我們使用編譯指令碼編譯之後,得到.i檔案如下:

//前面的內容忽略

# 3 "main.c" 2
# 12 "main.c"

# 12 "main.c"
int add(int a, int b)
{
 return (a + b);
}

int main(int argc, const char *argv[])
{
 int a = 6;
 int b = 7;

 printf("Hello world !n");
 printf("RETURN_MACRO: %dn", ({do {} while(0);1;}));
 printf("RETURN_MACRO2: %dn", 1);
 printf("a + b = %dn", ({int ret; ret = (a) + (b); ret;}));
 printf("a + b = %dn", ({int ret; ret = add((a), (b)); ret;}));

 return 0;
}

從.i檔案我們可以看到,宏定義被正常展開,下面確認下功能是否正常。

宏定義驗證

我們執行編譯出來的可執行檔案:

return_macro$ ./test 
Hello world !
RETURN_MACRO: 1
RETURN_MACRO2: 1
a + b = 13
a + b = 13

驗證成功。

經驗總結

  • 在C語言裡面,可以使用({aaa; bbb; ccc;})來實現宏定義帶返回值;這裡的返回值是最後一個;的值。
  • 注意裡面的()和{}都不能少,否則可能會破壞程式碼的語法結構,導致得不到正確的答案。

C語言中一些宏定義和常用的函數

typeof 關鍵字

如果你是 C++ 程式設計師,應該接觸過 C++11 裡的 decltype 操作符,它的作用是自動推導表示式的資料型別,以解決泛型程式設計中有些型別由模板引數決定而難以(甚至不可能)表示的問題。

其實這個特性在 C 語言中也早有類似的實現,GNU C 標準中的一個擴充套件特性 typeof 作用與 decltype 類似。

__typeof__ (ret) errnum = (ret); 

snprintf()函數的作用

#include<stdio.h>
int snprintf(char* dest_str,size_t size,const char* format,...);

【函數功能】:

先將可變引數 “…” 按照format的格式格式化為字串,然後再將其拷貝至dest_str中。

如果格式化後的字串長度小於size,則將字串全部拷貝至dest_str中,並在字串結尾處加上‘’; 如果格式化後的字串長度大於或等於size,則將字串的(size-1)拷貝至dest_str中,然後在字串結尾處加上’’. 函數返回值是 格式化字串的長度。

__builtin_expect的作用

__builtin_expect(errnum != 0, 0)

這個指令是gcc引入的,作用是"允許程式設計師將最有可能執行的分支告訴編譯器"。

這個指令的寫法為:__builtin_expect(EXP, N)。意思是:EXP==N的概率很大。

一般的使用方法是將__builtin_expect指令封裝為LIKELY和UNLIKELY宏。

C語言中常用的預定義

  • __LINE__:當前程式行的行號,表示為十進位制整型常數
  • __FILE__:當前原始檔名,表示字串型常數
  • __DATE__:轉換的日曆日期,表示為Mmm dd yyyy 形式的字串常數,Mmm是由asctime產生的。
  • __TIME__:轉換的時間,表示"hh:mm:ss"形式的字串型常數,是有asctime產生的。(asctime貌似是指的一個函數)
  • __STDC__:編輯器為ISO相容實現時位十進位制整型常數
  • __func__:它指示所在的函數
  • __assert_perror_fail:列印一條包含錯誤碼ERRNUM的錯誤訊息,並終止程式

反斜槓的作用

反斜槓起到換行作用,用於宏定義和字串換行。其中宏定義中使用居多。

如果一行程式碼有很多元素,導致太長影響閱讀,可以通過在結尾加的方式,實現換行,編譯時會忽略及其後的換行符,當做一行處理。

#define CHECK_ACTION_RETURN(expr) 
    if (!expr) { 
        printf(":failed(%d)n", ret); 
        return ret; 
                } else { 
        printf(":okn"); 
                }

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com