Подтвердить что ты не робот

Хорошая практика программирования для определения макросов (#define) в C

Например, никогда не определяйте макрос следующим образом:

#define DANGER 60 + 2

Это может быть опасно, если мы выполняем такую ​​операцию:

int wrong_value = DANGER * 2; // Expecting 124

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

#define HARMLESS (60 + 2)

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

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

4b9b3361

Ответ 1

Не только вы должны помещать parens вокруг аргументов, вы должны помещать 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 вместо С++, но все же помните его.

Ответ 2

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

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

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

  • Ему нужна конечная точка с запятой
  • Он работает с вложением и фигурными скобками, например. с if/else

Ответ 3

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

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

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

MAX(a++, b);

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


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


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

#define pint int *

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

pint a, b;

Вместо этого используйте typedefs.

Ответ 4

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

static const int DANGER = 60 + 2;

Ответ 5

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

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

Ответ 6

Ответ на макросы MAX/MIN, взятые из GCC-хаки в ядре Linux:

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

Ответ 7

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

#define MAX 10

может легко столкнуться с другим кодом, поэтому:

#define MYPROJECT_MAX 10

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

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

Ответ 8

Отмените определение макросов.

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

Ответ 9

Если вы осторожны и эксперт, вы можете выполнить код 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 value, given its name
// .. you do it ..

Теперь, если вы хотите добавить новую переменную, удалите ее или переименуйте ее, выполните однострочное редактирование.

Ответ 10

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

Ответ 11

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

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

Всегда добавляйте их так:

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