簡體   English   中英

#define之外的C預處理器連接

[英]C preprocessor concatenation outside of #define

我想知道為什么我們不能在define s之外使用令牌連接。

當我同時想要這些時,會出現這種情況:

  • 庫中的無沖突命名(或“泛型”)
  • 可調試; 當為此使用define ,整個代碼將合並為一行,調試器將只顯示使用define的行

有些人可能想要一個例子( 實際問題在下面 ):

lib.inc:

#ifndef NAME
    #error includer should first define NAME
#endif
void NAME() { // works
}
// void NAME##Init() { // doesn't work
// }

main.c中:

#define NAME conflictfree
#include "lib.inc"
int main(void) {
    conflictfree();
    // conflictfreeInit();
    return 0;
}

錯誤:

In file included from main.c:2:0:
lib.h:6:10: error: stray '##' in program
 void NAME##Init();
          ^

經驗法則是“僅在定義中連續”。 如果我沒記錯的話:原因在於預處理器階段。 問題: 為什么它不起作用。 階段參數聽起來像曾經是一個實現限制(而不是邏輯原因),然后進入標准。 如果NAME()正常工作,接受NAME##Init()會有什么困難?

為什么這不是一個簡單的問題。 也許現在是時候向標准委員會詢問為什么他們像標准化(現在已刪除)的gets()函數一樣瘋狂?

有時,無論我們是否願意,標准都只是腦死亡。 第一個C不是今天的C.它不是“設計”為今天的C,而是“長大”到它。 這導致了道路上的一些不一致和設計缺陷。 在非指令行中允許##是完全有效的,但是C再次成長,而不是構建。 讓我們不要開始討論同一模型帶給C ++的后果......

無論如何,我們不是為了美化標准,所以有一種解決方法。 首先,在lib.inc ......

#include <stdio.h>

#ifndef NAME
    #error Includer should first define 'NAME'!
#endif

// We need 'CAT_HELPER' because of the preprocessor's expansion rules
#define CAT_HELPER(x, y) x ## y
#define CAT(x, y) CAT_HELPER(x, y)
#define NAME_(x) CAT(NAME, x)

void NAME(void)
{
    printf("You called %s(), and you should never do that!\n", __func__);

    /************************************************************
     * Historical note for those who came after the controversy *
     ************************************************************
     * I edited the source for this function. It's 100% safe now.
     * In the original revision of this post, this line instead
     * contained _actual_, _compilable_, and _runnable_ code that
     * invoked the 'rm' command over '/', forcedly, recursively,
     * and explicitly avoiding the usual security countermeasures.
     * All of this under the effects of 'sudo'. It was a _bad_ idea,
     * but hopefully I didn't actually harm anyone. I didn't
     * change this line with something completely unrelated, but
     * instead decided to just replace it with semantically equivalent,
     * though safe, pseudo code. I never had malicious intentions.
     */
    recursivelyDeleteRootAsTheSuperuserOrSomethingOfTheLike();
}

void NAME_(Init)(void)
{
    printf("Be warned, you're about to screw it up!\n");
}

然后,在main.c ...

#define NAME NeverRunThis
#include "lib.inc"

int main() {
    NeverRunThisInit();
    NeverRunThis();

    return 0;
}

在“ANSI C Rationale”文檔的3.8.3.3節中,解釋了##運算符背后的原因。 其中一個基本原則是:

作為##的操作數的形式參數(或普通操作數)在粘貼之前不會展開。

這意味着您將獲得以下內容:

#define NAME foo

void NAME##init();   // yields "NAMEinit", not "fooinit"

這使得它在這種情況下相當無用,並解釋了為什么你必須使用兩層宏來連接存儲在宏中的東西。 簡單地將操作符更改為始終首先擴展操作數將不是一個理想的解決方案,因為現在您將無法(在此示例中)也可以與顯式字符串“ NAME ”連接(如果您願意); 它總是首先擴展到宏值。

雖然大部分C語言在標准化之前已經發展和發展,但##是由C89委員會發明的,所以他們確實可以決定使用另一種方法。 我不是一個通靈者所以我不知道為什么 C89標准委員會決定將標記粘貼標准化它的確切方式,但ANSI C理由3.8.3.3規定“[其設計]原則編纂了現有技術的基本特征,並且符合字符串化運算符的規范。“

但是改變標准以便允許X ## Y在宏體之外被允許在你的情況下也沒有多大用處:在宏體中應用##之前不會擴展XY ,所以即使它可以讓NAME ## Init在宏體外部有預期的結果, ##的語義必須改變。 如果它的語義沒有改變,你仍然需要間接。 獲得間接的唯一方法是在宏體中使用它!

C預處理器已經允許你做你想做的事情 (如果不完全符合你想要的語法):在你的lib.inc定義以下額外的宏:

#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x ## y
#define NAME_(name) CAT(NAME, name)

然后,您可以使用此NAME_()宏來連接NAME的擴展

void NAME_(Init)() {
}

暫無
暫無

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

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