ВходНаше всё Теги codebook PARTS Поиск Опросы Закон Среда
1 декабря
/1035467
Топик полностью
fk0, легенда (11.09.2020 03:09, просмотров: 463) ответил Aleksey_75 на )) оке
Классика жанра же: когда делаешь макрос, его всегда, кроме случаев когда невозможно, нужно делать выражением (а не оператором -- потому, что сам макрос могут вставить в контекст выражения, а не оператора), и обязательно брать в скобки тело макроса! И аргументы макроса тоже нужно брать в скобки, потому, что в общем случае неизвестно с каким аргументами вызывали макрос, может там MACRO(a+b, a*b), и неизвестно как оно состыкуется с операторами в макросе. 

Пример:


 #define MACRO(x, y) ((x)+(y))


Кроме того, если в макрос передаются аргументы-переменные например (любые идентификаторы или типы на самом деле), они могут конфликтовать с переменными или типами продекларированными в теле макроса (это если макрос таки не выражение, а оператор, или если макрос на C++ и содержит лямбду, или если макрос на GCC и содержит statement expressions). Поэтому декларировать что-то внутри макроса лучше с какими-нибудь префиксами/суффиксами, а не просто A или Б.


Пример:


#define MACRO(count, f) do { 
        int n__ = count; \
        do f(n__) while (--n__); \
} while(0)

// MACRO(n__,f) gives an error because of name clashing.


Здесь так же показано, что макросы являющиеся оператором стоит обязательно включить в блок кода (фигурные скобки), который может быть внутри "do {} while(0)", что позволит в конце макроса вставить точку с запятой. Если без do/while то в таком варианте будет ошибка:


int g(void);
int f(void);

// this gives an error:
#define MACRO() { g(); }

// this compiles without error
//#define MACRO() do { g(); } while(0)

int main(int ac, char *av[])
{
    (void)av;
    
    if (ac)
       MACRO();
    else
       f();
       
    return 0;   
}

main.cpp:17:5: error: 'else' without a previous 'if'
   17 |     else
      |     ^~~~


https://coliru.stacked-crooked.com/a/6fee23d2ad027e9e



Пример макроса для GCC:


#define MACRO(n, f) ({ \
        int n__ = n; \
        do f(n__) while (--n__); \
})


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


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


1) макросы именуются строго заглавными буквами: #define MACRO(x, y) ((x)+(y))

2) типы обозначаются в CamelCase (PascalStyle): typedef struct S { ... } MyType;

3) объекты именуются в snake_case или в lowerCamelCase : MyType my_object или MyType myObject;

4) функции именуются в snake_case или lowerCamelCase тоже (функцию легко перепутать с конструктором типа в C++ обычно...);


См. также: http://caxapa.ru/910140


Примеры сложных макросов:

http://caxapa.ru/964034

http://caxapa.ru/964114


Расширения GCC:

https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html

https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html

[ZX]
code stylemacrofk0
Ответить
Ответы