web-dev-qa-db-ja.com

Cでのマクロ定義(#define)の優れたプログラミング手法

たとえば、次のようなマクロは絶対に定義しないでください。

#define DANGER 60 + 2

次のような操作を行うと、これは潜在的に危険な場合があります。

int wrong_value = DANGER * 2; // Expecting 124

代わりに、マクロのユーザーがどのように使用するかわからないため、次のように定義します。

#define HARMLESS (60 + 2)

例は簡単ですが、それは私の質問をほぼ説明しています。マクロを作成するときに推奨する一連のガイドラインまたはベストプラクティスはありますか?

御時間ありがとうございます!

24
Srikanth

引数の周りに親を配置するだけでなく、返される式の周りに親を配置する必要があります。

_#define MIN(a,b)  a < b ? a : b     // WRONG  

int i = MIN(1,2); // works
int i = MIN(1,1+1); // breaks

#define MIN(a,b)  (a) < (b) ? (a) : (b)   // STILL WRONG

int i = MIN(1,2); // works
int i = MIN(1,1+1); // now works
int i = MIN(1,2) + 1; // breaks

#define MIN(a,b)  ((a) < (b) ? (a) : (b))   // GOOD

int i = MIN(1,2); // works
int i = MIN(1,1+1); // now works
int i = MIN(1,2) + 1; // works
_

ただし、MIN(3,i++)はまだ壊れています...

最良のルールは他のアプローチが機能しない場合にのみ#definesを使用することです! C++ではなくCについて質問していることは知っていますが、それでも彼のことを覚えておいてください。

29
Roddy

引数を実行して式のように動作するマクロを実行する場合、これは慣用的です。

 #define DOIT(x) do { x } while(0)

このフォームには次の利点があります。

  1. 終了するセミコロンが必要です
  2. ネストとブレースで機能します。 if/elseで
29
unwind

マクロ全体を括弧で囲みますおよび展開リストで参照されている各引数を囲みます。

#define MAX(x, y) ((x) > (y) ? (x) : (y))

引数を複数回評価するマクロを作成することは避けてください。このようなマクロは、引数に副作用がある場合、期待どおりに動作しません。

MAX(a++, b);

abより大きい場合、a++を2回評価します。


マクロに大文字の名前を使用して、それが関数ではなくマクロであることを明確にし、それに応じて違いを考慮できるようにします(別の一般的なグッドプラクティスは、関数に副作用のある引数を渡さないことです)。


次のようなタイプの名前を変更するためにマクロを使用しないでください。

#define pint int *

誰かが入力すると期待どおりに動作しないため

pint a, b;

代わりにtypedefを使用してください。

10
Robert Gamble

整数またはその他の定数値には、マクロの代わりに静的const値を使用します。多くの場合、コンパイラーはそれらを最適化することができ、言語の型システムでは一流の市民のままです。

static const int DANGER = 60 + 2;
6
John Dibling

展開では、引数を括弧で囲み、式を渡した場合に意図した動作が得られるようにします。

#define LESS_THAN(X,Y) (((X) < (Y) ? (X) : (Y))
5
EvilTeach

MAX/MINマクロへの応答、 LinuxカーネルでのGCCハック

#define min(x, y) ({                       \
        typeof(x) _min1 = (x);             \
        typeof(y) _min2 = (y);             \
        (void) (&_min1 == &_min2);         \
        _min1 < _min2 ? _min1 : _min2; })
5
dalle

マクロの定義を解除します。

#defines#undefと一致する必要があります。これにより、プリプロセッサが詰まって意図しないコードに影響を与えるのを防ぎます。

2
lillq

マクロにはグローバルスコープがあり、他のものと衝突する可能性があるため、マクロにはかなり一意の名前を使用してください。

#define MAX 10

他のコードと簡単に衝突する可能性があるため、次のようにします。

#define MYPROJECT_MAX 10

またはさらにユニークなものが良いでしょう。

この種の衝突ではコンパイルエラーが発生しなかったが、わずかに間違ったコードが生成されたため、非常に陰湿な場合があります。

2
DarthPingu

複数行のマクロの場合は、do { } while (0)を使用します。

#define foo(x) do {  \
    (x)++;           \
    printf("%d", x); \
} while(0)

やった?

#define foo(x) {     \
    (x)++;           \
    printf("%d", x); \
}

代わりに、

if (xyz)
    foo(y);
else
    foo(z);

失敗したでしょう。

また、マクロに一時変数を導入するときは注意してください。

#define foo(t) do {    \
    int x = (t);       \
    printf("%d\n", x); \
} while(0)

int x = 42;
foo(x);

0ではなく42を出力します。

値を返す必要がある複雑な式がある場合は、コンマ演算子を使用できます。

#define allocate(foo, len) (foo->tmp = foo->head, foo->head += len, foo->tmp)
2
sanjoyd

注意深く専門家であれば、マクロを単純なコードジェネレーターとして使用することで、DRY(Do n't-Repeat-Yourself)コードを実行できる可能性があります。他の人に説明する必要があります。プログラマーはあなたがしていることをしますが、それは多くのコードを節約することができます。例えば、リストマクロ技術:

// define a list of variables, error messages, opcodes
// or anything that you have to write multiple things about
#define VARLIST \
    DEFVAR(int, A, 1) \
    DEFVAR(double, B, 2) \
    DEFVAR(int, C, 3) \

// declare the variables
#define DEFVAR(typ, name, val) typ name = (val);
    VARLIST
#undef  DEFVAR

// write a routine to set a variable by name
void SetVar(string varname, double value){
    if (0);
    #define DEFVAR(typ, name, val) else if (varname == #name) name = value;
        VARLIST
    #undef  DEFVAR
    else printf("unrecognized variable %s\n", varname);
}

// write a routine to get a variable's value, given its name
// .. you do it ..

これで、新しい変数を追加したり、削除したり、名前を変更したりする場合は、1行の編集になります。

1
Mike Dunlavey

私がこれをどのように嫌うかを見てください:

void bar(void) {
    if(some_cond) {
        #define BAZ ...
        /* some code */
        #undef BAZ
    }
}

常に次のように配置します。

void bar(void) {
    if(some_cond) {
#define BAZ ...
        /* some code */
#undef BAZ
    }
}