2016-03-10 18 views
29

なぜdefineの外のトークン連結を使用することはできません。私は、同時にこれらをしたいときCプリプロセッサ連結#defineの外

これがアップします:

  • コンフリクト・フリーのライブラリで命名(または「ジェネリック」用)
  • debugability。このため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(); 
     ^

親指のルールは、 "唯一の定義でCONCAT" です。そして、私が正しく覚えていれば、理由はプリプロセッサフ​​ェーズのためです。 質問:なぜが動作しません。フェーズ - 引数は、一度実装上の制限(論理的な理由ではなく)だったのと同じように聞こえ、その後標準に入りました。 NAME()がうまくいけば、NAME##Init()を受け入れることがどうして難しいでしょうか?

+3

まあ、非常に純粋なことは、プリプロセッサのディレクティブは常に '#'で始まっているということです。私はプリプロセッサの開発者が他のものを解析するのは怠惰だと思います。 –

+2

おそらくC委員会にそれを受け入れるように説得することができれば、それは働かせることができます。しかし、 'NAME ## Init'と' #define NAME_(x)NAME ## x'と 'NAME_(Init)'の利点はおそらくそれほど価値がありません。 – rodrigo

+2

@EugeneSh:しかし、プリプロセッサは、マクロを使用するときに、識別して置き換えるために、_all the code_を読み取る必要があります。この提案はそれを隣接する '## 'に拡張するだけです。 – rodrigo

答えて

36

##オペレータの背後にある理由説明する。基本原則の一つは述べて:

A formal parameter (or normal operand) as an operand for ## is not expanded before pasting.

これは、あなたが以下のようになるだろうことを意味します

#define NAME foo 

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

これは、この文脈では、それはむしろ無用になり、そしてあなたは、マクロの2層を使用する必要が理由を説明しますマクロに格納されているものを連結します。オペランドを常に変更してオペランドを最初に展開するのは理想的な解決策ではありません。なぜなら、(この例では)明示的な文字列 "NAME"を連結できないからです。最初に常にマクロ値に展開されます。

79

なぜであったかは簡単な質問ではありません。たぶん、今度は標準化委員会(なぜ今取り除かれた)gets()の機能を標準化するのが狂っていたのか、標準委員会に尋ねる時でしょうか?

時には、私たちが望むかどうかにかかわらず、標準は単に脳死です。最初のCは今日のCではありませんでした。それは今日のCになるように設計されていませんでしたが、その中に成長しました。これにより道路にはかなりの矛盾と設計上の欠陥が生じています。 ##を非指示行に入れることは完全に有効でしたが、やはりCはビルドされずに拡張されました。 そして、同じモデルがC++にもたらされた結果について話を始めません...

とにかく、これを回避するための方法はありません。 ... main.cで、

#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"); 
} 

その後... lib.incで、まず第一に

文書 "ANSI C理由" のセクション3.8.3.3で
#define NAME NeverRunThis 
#include "lib.inc" 

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

    return 0; 
} 
9

C言語の多くは、その標準化の前に進化して開発していたが、##C89委員会によってを発明したので、確かに彼らは同様に別のアプローチを使用することを決定した可能性があります。私は霊魂ではないので、私は言えませんなぜ C89標準委員会は正確にどのようにトークン貼り付けを標準化することを決めましたが、ANSI Cの根拠3.8.3.3は、"[その設計]原則は、文字列化演算子の仕様と一致しています。

しかしX ## Yはあなたのケースで非常に有用であることではないでしょうマクロボディの外に許されるように基準を変更する次のいずれかXまたはY##前に展開されないので場合でも、いずれかのマクロ体に適用されますNAME ## Initにマクロ本体の外側に意図した結果を持たせることは可能ですが、##のセマンティクスは変更する必要があります。そのセマンティクスは変更されていませんでしたが、インダイレクションが必要です。とにかく、間接参照を取得する唯一の方法は、マクロ本体内で使用することです。

C preprocessor already allows you to do what you want to do(ない正確にあなたがしたいと思うの構文を使用している場合):

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

は、その後、あなたはNAME

の拡大を連結するには、この NAME_()マクロを使用することができます:あなたの lib.inc定義し、次の余分なマクロで
void NAME_(Init)() { 
}