2008-11-26 2 views
23

Например, никогда не определяют макрос так:Надлежащая практика программирования макроопределений (#define) в C

#define DANGER 60 + 2 

Это может быть потенциально опасным, когда мы делаем операцию так:

int wrong_value = DANGER * 2; // Expecting 124 

Вместо этого определить, как это потому, что вы не знаете, как пользователь макроса может использовать:

#define HARMLESS (60 + 2) 

пример я тривиально, но это в значительной степени объясняет мой вопрос. Есть ли какой-либо набор рекомендаций или рекомендаций, которые вы бы рекомендовали при написании макроса?

Спасибо за ваше время!

+1

Это не вопрос. Это заявление. – BIBD 2008-11-26 15:42:24

ответ

26

Не только вы должны помещать парсеры вокруг аргументов, вы должны поставить parens вокруг возвращаемого выражения.

#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++, но все же помните его.

5

В расширении поместите круглые скобки вокруг аргументов, чтобы, если они передаются в выражении, вы получите предполагаемое поведение.

#define LESS_THAN(X,Y) (((X) < (Y) ? (X) : (Y)) 
+1

thisw имеет ++/- ошибка – dsm 2008-11-26 16:31:14

+0

Ya. Не делай этого. – EvilTeach 2008-11-26 18:21:14

28

При выполнении макроса, который должен запустить свой аргумент и вести себя как выражение, это идиоматическое:

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

Эта форма имеет следующие преимущества:

  1. Он нужен конечная точка с запятой
  2. Работает с гнездом и скобками, например с if/else
+1

А что, если `x` - всего лишь одно утверждение? Например, `foobar = 42`, нам все равно нужно заключить его в` do {} while (0) `или просто положить его в круглые скобки? – 2011-09-03 04:10:49

+1

Вам все равно понадобится, если вы хотите: 1) макрокоманду ** запросить ** завершающую точку с запятой и 2) использовать ее как отдельную инструкцию в однострочном if-statement. – 2011-11-05 20:46:23

+1

Проверьте эту ссылку. Это объясняет это очень хорошо: http://kernelnewbies.org/FAQ/DoWhile0 – 2011-11-05 20:47:06

6

использовать значения статических констант вместо макросов для постоянных значений, интегральных или других. Компилятор часто может их оптимизировать, и они остаются гражданами 1-го класса в системе типа языка.

static const int DANGER = 60 + 2; 
+0

Будет ли это действительно работать в стандартном C? В файле заголовка? – Roddy 2008-11-26 15:51:57

+0

@Roddy: нет, он не будет работать в C в заголовке - вместо этого используйте перечисление. – 2008-11-26 17:48:05

2

Использование достаточно уникальных имен для макросов, так как они имеют глобальный масштаб и может конфликтовать ни с чем, так:

#define MAX 10 

легко может конфликтовать с другим кодом, так:

#define MYPROJECT_MAX 10 

или что-то еще более уникальным, было бы лучше.

Я видел случаи, когда такое столкновение не вызывало ошибку компиляции, но порождало неверный код, поэтому оно может быть весьма коварным.

10

Используйте круглые скобки вокруг всей макро и вокруг каждого аргумента, указанного в списке расширения:

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

Избегайте написания макросов, которые оценивают свои аргументы несколько раз. Такие макросы не будут вести себя, как ожидается, когда аргументы имеют побочные эффекты:

MAX(a++, b); 

оценит a++ дважды, если a больше b.


Используйте ВЕРХНИЕ имена для макросов, чтобы понять, что это макрос, а не функция, так что различия можно считать соответствующим образом (другая вообще хорошая практика не передавать аргументы, которые имеют побочные эффекты на функцию либо).


Не использовать макросы для переименования типов, как это:

#define pint int * 

, потому что он не будет вести себя так, как и ожидалось, когда кто-то типа

pint a, b; 

Используйте вместо определения типов.

1

Если вы осторожны и компетентны, вы можете выполнить код DRY (Don'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-линия редактирования.

4

Ответ на макросы MAX/MIN, взяты из GCC hacks in the Linux kernel:

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

Посмотрите, как я ненавижу это:

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

Всегда ставьте их так:

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

Undefine ваши макросы.

Ваш #defines должен соответствовать #undef. Это предотвращает засорение препроцессора и повреждение непреднамеренных фрагментов кода.

1

Для нескольких макросов линии, используйте 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) 
Смежные вопросы