Какие уловки я могу использовать с макросами?
В нашем устаревшем коде, а также нашем современном коде, мы используем макросы для выполнения отличных решений, таких как генерации кода и т.д. И мы используем операторы #
и ##
.
Мне любопытно, как другие разработчики используют макросы, чтобы делать классные вещи, если они вообще их используют.
Ответы
Ответ 1
В C обычно определяется макрос, который делает некоторые вещи, получая дословный аргумент, и в то же время определяет функции, чтобы иметь возможность прозрачно получать адрес.
// could evaluate at compile time if __builtin_sin gets
// special treatment by the compiler
#define sin(x) __builtin_sin(x)
// parentheses avoid substitution by the macro
double (sin)(double arg) {
return sin(arg); // uses the macro
}
int main() {
// uses the macro
printf("%f\n", sin(3.14));
// uses the function
double (*x)(double) = &sin;
// uses the function
printf("%f\n", (sin)(3.14));
}
Ответ 2
Самый крутой макрос: assert, include guard, __FILE__, __LINE__.
Избегайте использования другого макроса в коде.
EDIT:
Используйте макросы только тогда, когда у вас нет законного решения без них.
Ответ 3
Существует также идиома X Macro, которая может быть полезна для DRY и простого генерации кода:
Определяет в заголовке gen.x вид таблицы, используя макрос еще не определен:
/** 1st arg is type , 2nd is field name , 3rd is initial value , 4th is help */
GENX( int , "y" , 1 , "number of ..." );
GENX( float , "z" , 6.3 , "this value sets ..." );
GENX( std::string , "name" , "myname" , "name of ..." );
Затем он может использовать его в разных местах, определяя его для каждого #include с обычно другим определением:
class X
{
public :
void setDefaults()
{
#define GENX( type , member , value , help )\
member = value ;
#include "gen.x"
#undef GENX
}
void help( std::ostream & o )
{
#define GENX( type , member , value , help )\
o << #member << " : " << help << '\n' ;
#include "gen.x"
#undef GENX
}
private :
#define GENX( type , member , value , help )\
type member ;
#include "gen.x"
#undef GENX
}
Ответ 4
Вы можете посмотреть Boost.Preprocessor, чтобы найти много интересных применений препроцессора...
Ответ 5
SHOW() для отладки:
#define SHOW(X) cout << # X " = " << (X) << endl
Двойная оценка, чтобы развернуть аргумент аргументов: (Например, используйте фактический номер строки, а не "__LINE __".)
/* Use CONCATENATE_AGAIN to expand the arguments to CONCATENATE */
#define CONCATENATE( x,y) CONCATENATE_AGAIN(x,y)
#define CONCATENATE_AGAIN(x,y) x ## y
Статические утверждения времени компиляции.
например:.
#define CONCATENATE_4( a,b,c,d) CONCATENATE_4_AGAIN(a,b,c,d)
#define CONCATENATE_4_AGAIN(a,b,c,d) a ## b ## c ## d
/* Creates a typedef that legal/illegal depending on EXPRESSION. *
* Note that IDENTIFIER_TEXT is limited to "[a-zA-Z0-9_]*". *
* (This may be replaced by static_assert() in future revisions of C++.) */
#define STATIC_ASSERT( EXPRESSION, IDENTIFIER_TEXT) \
typedef char CONCATENATE_4( static_assert____, IDENTIFIER_TEXT, \
____failed_at_line____, __LINE__ ) \
[ (EXPRESSION) ? 1 : -1 ]
Используется через:
typedef int32_t int4;
STATIC_ASSERT( sizeof(int4) == 4, sizeof_int4_equal_4 );
Инициализация экземпляра класса CodeLocation: (Хранение файла/строки/функции из точки вызова - это может * ТОЛЬКО * делать только с помощью макроса или путем прямого доступа к макросам __FILE __/__ LINE __/etc в исходной точке. )
/* Note: Windows may have __FUNCTION__. C99 defines __func__. */
#define CURRENT_CODE_LOCATION() \
CodeLocation( __PRETTY_FUNCTION__, __FILE__, __LINE__ )
Впоследствии используется макросами MESSAGE/WARN/FAIL как удобный механизм печати источника. Например:
#define WARN_IF_NAN(X) \
do \
{ \
if ( isnan(X) != 0 ) \
WARN( # X " is NaN (Floating Point NOT-A-NUMBER)" ); \
if ( isinf(X) != 0 ) \
WARN( # X " is INF (Floating Point INFINITY)" ); \
} while ( false )
Ассемблер/без макросов. Вы можете передать любой токен, включая операторы типа '==', через макрос. Таким образом, конструкции:
ASSERT( foo, ==, bar )
или
UNLESS( foo, >=, 0, value=0; return false; );
Являются законными. Утверждение/Если макросы не могут автоматически добавлять всевозможные полезные сведения, такие как CodeLocation, трассировать стек или бросать исключения/вырезать/изящно изящно.
Сделать errno проще:
#define ERRNO_FORMAT "errno= %d (\"%s\")"
#define ERRNO_ARGS errno, strerror(errno)
#define ERRNO_STREAM "errno= " << errno << " (\"" << strerror(errno) << "\") "
например. printf ( "Открыть не удалось". ERRNO_FORMAT, ERRNO_ARGS);
Ответ 6
Один из моих любимых трюков - способ передать переменное количество аргументов в макросы, чтобы впоследствии использоваться при вызове printf-подобных функций. Для этого я указываю, что макрос имеет только один параметр и использует его в теле макроса без(), но передает все параметры макросу в ((и)), поэтому список выглядит как один аргумент. Например,
#define TRACE( allargs) do { printf allargs; } while ( 0)
...
TRACE(( "%s %s\n", "Help", "me"));
Ответ 7
Регистрация - это одно место, где часто используются макросы:
#define LOG(log) \
if (!log.enabled()) {} \
else log.getStream() << __FILE__ << "@" << __LINE__ << ": "
log_t errorlog;
...
LOG(errorlog) << "This doesn't look good:" << somedata;
Ответ 8
Я благодарю Шона Барретта за этот веселый:
#ifndef blah
#define blah(x) // something fun
#include __FILE__
#undef blah
#endif
#ifndef blah
#define blah(x) // something else that is also fun
#include __FILE__
#undef blah
#endif
#ifdef blah
blah(foo)
blah(bar)
#endif
Хакерный способ заставить препроцессор генерировать код для вас на основе некоторой структуры более высокого уровня, которую вы можете выражать с помощью макросов.
Ответ 9
Основное место, где я использую макросы, находится в моей собственной тестовой среде. Например, когда я хочу утверждать, что какой-то код должен бросать, я использую этот макрос:
#define MUST_THROW( expr )
try {
(expr);
(myth_suite_).Fail( #expr +
std::string( " should throw but didn't" ) );
}
catch( ... ) {
}
И используйте его следующим образом:
MUST_THROW( some_bogus_stuff() );
MUST_THROW( more_bogus_stuff() );
Единственное, что я использую, это объявление классов. У меня макрос:
#define CANNOT_COPY( cls ) \
private: \
cls( const cls & ); \
void operator=( const cls & ) \
который я использую, чтобы указать, что класс нельзя скопировать (или присвоить):
class BankAccount {
CANNOT_COPY( BankAccount );
....
};
это не делает ничего особенного, но привлекает внимание людей и легко может быть найдено.
Ответ 10
Я часто переношу такие вещи, как debug sonar, в простой макрос, который позволяет скомпилировать его из релизов:
#ifdef DEBUG
#define D(s) do { s; } while(0)
#else
#define D(s) do {/**/} while(0)
#endif
Использование позже обычно выглядит примерно так:
D(printf("level %d, condition %s\n", level, condition));
Идиома do{}while(0)
позволяет избежать проблем, которые могут возникнуть в результате случайного использования D(...)
единственного содержимого условного или цикла. В конце концов, вы не хотите, чтобы такой код означал не ту вещь:
for(i=1;i<10;++i) D(printf("x[%d]=%f\n",i,x[i]));
SomeReallyExpensiveFunction(x);
Если бы я мог сделать этот случай ошибкой, я бы, но препроцессор должен был быть полным компилятором, чтобы сказать, что макрос D()
был единственным содержимым тела цикла.
Я также большой поклонник утверждений времени компиляции. Моя формулировка несколько отличается, но не имеет реальных преимуществ перед другими, которые я видел. Ключ состоит в том, чтобы сформировать однозначно названный typedef, который выдает ошибку, если утверждаемое условие ложно, а не иначе. В cassert.h мы имеем:
/*! \brief Compile-time assertion.
*
* Note that the cassert() macro generates no code, and hence need not
* be restricted to debug builds. It does have the side-effect of
* declaring a type name with typedef. For this reason, a unique
* number or string of legal identifier characters must be included
* with each invocation to avoid the attempt to redeclare a type.
*
* A failed assertion will attempt to define a type that is an array
* of -1 integers, which will throw an error in any standards
* compliant compiler. The exact error is implementation defined, but
* since the defined type name includes the string "ASSERTION" it
* should trigger curiosity enough to lead the user to the assertion
* itself.
*
* Because a typedef is used, cassert() may be used inside a function,
* class or struct definition as well as at file scope.
*/
#define cassert(x,i) typedef int ASSERTION_##i[(x)?1:-1]
И в каком-то исходном файле где-нибудь в typedef будет законным:
#include "cassert.h"
...
cassert(sizeof(struct foo)==14, foo1);
...
Результирующее сообщение об ошибке часто неясно, но будет содержать фрагмент идентификатора, позволяющий открывать строку нарушения грубой силой.
Я был виновен в использовании препроцессора в тех местах, где предпочтительным был вариант написания программы генерации кода, как и код в другом ответе, который генерировал множество котельных табличек на основе уникальных частей члена перечисления имя. Это особенно удобно при написании большого количества писем-сообщений для компиляции в C.
Ответ 11
Для встроенного кода хороший трюк от embeddedgurus.com
позволяет обрабатывать двоичные значения:
B8(01010101) // 85
B16(10101010,01010101) // 43,605
B32(10000000,11111111,10101010,01010101) // 2,164,238,93
Это позволяет достичь аналогичных целей, как и предыдущий ответ от @Ferruccio о BOOST_BINARY, хотя и немного расширен.
Здесь код (скопирован, не протестирован, см. ссылку для получения дополнительной информации)
// Internal Macros
#define HEX__(n) 0x##n##LU
#define B8__(x) ((x&0x0000000FLU)?1:0) \
+((x&0x000000F0LU)?2:0) \
+((x&0x00000F00LU)?4:0) \
+((x&0x0000F000LU)?8:0) \
+((x&0x000F0000LU)?16:0) \
+((x&0x00F00000LU)?32:0) \
+((x&0x0F000000LU)?64:0) \
+((x&0xF0000000LU)?128:0)
// User-visible Macros
#define B8(d) ((unsigned char)B8__(HEX__(d)))
#define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb))
#define B32(dmsb,db2,db3,dlsb) \
(((unsigned long)B8(dmsb)<<24) \
+ ((unsigned long)B8(db2)<<16) \
+ ((unsigned long)B8(db3)<<8) \
+ B8(dlsb))
Мне нравятся макросы. Так много удовольствия при отладке!
Ответ 12
Можно упростить повторяющиеся вещи, т.е. перечисление перечисляет
enum {
kOneEnum,
kTwoEnum,
kThreeEnum,
kFourEnum
};
... и позже сделать случай переключения по структурированному пути
#define TEST( _v ) \
case k ## _v ## Enum: \
CallFunction ## _v(); \
break;
switch (c) {
TEST( One );
TEST( Two );
TEST( Three );
TEST( Four );
}
Примечание. Это можно сделать с помощью массива указателей функций, но это открывает несколько дополнительных возможностей для добавления параметров, а также использует расширения строк с единственным хешем.
... или проверить на строки, чтобы получить правильное значение перечисления
int value = -1;
char *str = getstr();
#define TEST( _v ) \
if (!strcmp(# _v, str)) \
value = k ## _v ## Enum
TEST( One );
TEST( Two );
TEST( Three );
TEST( Four );
Ответ 13
Вы можете использовать макросы для определения одинаковой функциональности с разными типами данных. Например:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#define DEFINE_BITS_STR(name, type) \
char *bits_str_##name(type value) \
{ \
int len = sizeof(type) * CHAR_BIT; \
char *result; \
type n; \
int i; \
\
result = (char *)calloc(len+1, sizeof(type)); \
if(result == NULL) \
return NULL; \
\
memset(result, '0', len); \
result[len] = 0x00; \
\
n = value; \
i = len; \
while(n) \
{ \
if(n & 1) \
result[i] = '1'; \
\
n >>= 1; \
--i; \
} \
\
return result; \
}
DEFINE_BITS_STR(uchar, unsigned char)
DEFINE_BITS_STR(uint, unsigned int)
DEFINE_BITS_STR(int, unsigned int)
int main()
{
unsigned char value1 = 134;
unsigned int value2 = 232899;
int value3 = 255;
char *ret;
ret = bits_str_uchar(value1);
printf("%d: %s\n", value1, ret);
ret = bits_str_uint(value2);
printf("%d: %s\n", value2, ret);
ret = bits_str_int(value3);
printf("%d: %s\n", value3, ret);
return 1;
}
В этом примере определяются три функции (bits_str_uchar()
, bits_str_uint()
, bits_str_int()
), которые обрабатывают три разных типа данных (unsigned char
, unsigned int
, int
). Однако все возвращают строку, содержащую биты переданного значения.
Ответ 14
Из проекта CrashRpt нужен трюк для расширения макросов и определяет:
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);
Ответ 15
Когда вы реализуете COM-сервер, вам нужно позаботиться обо всех исключениях, которые может вызвать ваш код, - выдача исключения через границу метода COM часто приводит к сбою вызывающего приложения.
Способы скобок полезны для этого. Там находится открывающая скобка, которая представляет собой макрос, содержащий "try" и закрывающую скобку, которая содержит набор "catch" es, обертывание исключений в ErrorInfo и создание HRESULT.
Ответ 16
Строковые литералы со значениями по умолчанию (которые не равны нулю), используя C99 variadic macro
struct Example {
int from;
int to;
const char *name;
}
#define EXAMPLE(...) ((struct Example){.from=0, .to=INT_MAX, .name="", __VA_ARGS__})
с использованием EXAMPLE(.name="test")
использует значения по умолчанию, за исключением явного переопределения name
. Это затенение с последующим упоминанием одного и того же элемента хорошо определено в стандарте.
Ответ 17
Большинство (все?) фреймворков С++ Unit Testing основаны на макросах. Мы используем UnitTest ++. Проверьте это, чтобы увидеть всевозможные причудливые макросы.
Ответ 18
Макрос BOOST_BINARY выполняет некоторую хитрость предварительного процессора, чтобы дать С++ возможность выражать числовые константы в двоичном формате. Однако он ограничен 0-255.
Ответ 19
Преобразование их в конструкцию языка для улучшения безопасности и отладки типа.
Ответ 20
макросы утилиты pthreads особенно впечатляют IMHO.
Ответ 21
Когда я работаю над огромными вложенными структурами c/С++, подобными используемым для 3GPP RRC/NBAP/RNSAP, я следую этому трюку, чтобы код выглядел чистым.
struct leve1_1
{
int data;
struct level2
{
int data;
struct level3
{
int data;
} level_3_data;
} level_2_data;
} level_1_data;
level_1_data.data = 100;
#define LEVEL_2 leve1_1_data.level_2_data
LEVEL_2.data = 200;
#define LEVEL_3 LEVEL_2.level_3_data
LEVEL_3.data = 300;
#undef LEVEL_2
#undef LEVEL_3
Это облегчит жизнь во время технического обслуживания. Кроме того, время разработки и код будут считаны.
Ответ 22
void _zero_or_die(int v, const char* filename, int line)
{
if (v != 0)
{
fprintf(stderr, "error %s:%d\n", filename, line);
exit(1);
}
}
#define ZERO_OR_DIE_ for (int _i=1; _i == 1; _zero_or_die(_i, __FILE__, __LINE__)) _i=
ZERO_OR_DIE_ pipe(fd);
ZERO_OR_DIE_ close(0);
ZERO_OR_DIE_ sigaction(SIGSEGV, &sigact, NULL);
ZERO_OR_DIE_ pthread_mutex_lock(&mt);
ZERO_OR_DIE_ pthread_create(&pt, NULL, func, NULL);
Ответ 23
На микроконтроллерах обычно используется отладочный код с использованием UART, поскольку аппаратные точки останова имеют много недостатков.
Это простой макрос, который оказался очень полезным:
#define DEBUG_OUT(value) sprintf(uartTxBuf, "%s = 0x%04X\n", #value, value);\
puts_UART((uint16_t *) uartTxBuf)
Пример использования:
for (i=0; i < 4; i++)
{
DEBUG_OUT(i);
DEBUG_OUT(i % 3);
}
Полученный поток:
i = 0x0000
i % 3 = 0x0000
i = 0x0001
i % 3 = 0x0001
i = 0x0002
i % 3 = 0x0002
i = 0x0003
i % 3 = 0x0000
Да, это грубо и небезопасно. Он применяется только до тех пор, пока ошибка не будет изолирована, поэтому этот макрос не наносит вреда.
Ответ 24
Часто я использую это. У меня заголовок debug.h
определяют как следующее:
#ifndef DEBUG_H
#define DEBUG_H
#ifdef DEBUG
#define debuf if(1)
#else
#define debug if(0)
#endif
#endif
а затем:
debug {
printf("message from debug!");
}
если вы хотите получить сообщение "message from debug!"
, скомпилируйте с помощью:
gcc -D DEBUG foo.c
В противном случае ничего не происходит. Gcc - очень умный компилятор. Если DEBUG
не определено, сгенерированный if(0)
(мертвый код) будет удален из вашего кода с некоторыми оптимизациями.
Вы все еще можете сделать больше:
debug
{
pritnf("I'm in debug mode!\n");
}
else
{
printf("I'm not in debug mode\n");
}
Несколько дней назад я увидел, что D предоставляет функцию очень похожее.
Если вы считаете, что выше, без контекста, вы можете определить, как
#define in_debug if(1)
#define not_debug else
И затем
in_debug {
printf("I'm in debug mode!");
}
not_debug {
printf("Not in debug mode!");
}
Ответ 25
В макросах очень легко выполнять поток управления, потому что это просто замена текста. Вот пример с циклом for:
#include <stdio.h>
#define loop(i,x) for(i=0; i<x; i++)
int main(int argc, char *argv[])
{
int i;
int x = 5;
loop(i, x)
{
printf("%d", i); // Output: 01234
}
return 0;
}