Какие приемы я могу использовать с макросами?[закрыто]

StackOverflow https://stackoverflow.com/questions/650461

Вопрос

В нашем устаревшем коде, а также в нашем современном коде мы используем макросы для выполнения изящных решений, таких как генерация кода и т.д.И мы используем оба метода # и ## операторы.

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

Это было полезно?

Решение

В 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));
}

Другие советы

Самый крутой макрос - это:утвердить, включить охрану, __ФАЙЛ__, __СТРОКУ__.
Избегайте использования других макросов в вашем коде.

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

Существует также идиома X Macro, которая может быть полезна для СУХОГО и простого генерирования кода:

В заголовке 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
}

Вы можете взглянуть на Boost.Preprocessor чтобы найти множество интересных применений препроцессора ...

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's 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/Unless.Вы можете передать любой токен, включая такие операторы, как '==', через макрос.Итак, конструкции типа:

ASSERT( foo, ==, bar )

Или

UNLESS( foo, >=, 0, value=0; return false; );

Являются законными.Утверждать / Если макросы не могут автоматически добавлять всевозможную приятную полезную информацию, такую как CodeLocation, трассировки стека или корректное выбрасывание исключений / coredumping / exiting.


Упрощение 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 );

Один из моих любимых приемов - это способ передать переменное число аргументов в макросы, которые впоследствии будут использоваться, например, при вызове printf-подобных функций. Для этого я указываю, что макрос имеет только один параметр, и использую его в теле макроса без (), но передаю все параметры макросу в ((и)), поэтому список выглядит как один аргумент. Например,

#define TRACE( allargs) do { printf allargs; } while ( 0)
...
TRACE(( "%s %s\n", "Help", "me"));

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

#define LOG(log) \
  if (!log.enabled()) {} \
  else log.getStream() << __FILE__ << "@" << __LINE__ << ": "


log_t errorlog;
...

LOG(errorlog) << "This doesn't look good:" << somedata;

Я благодарю Шона Барретта за это забавное:

#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

Хакерский способ заставить препроцессор генерировать для вас код на основе структуры более высокого уровня, которую вы можете выразить с помощью макросов.

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

#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 );
    ....
};

это не делает ничего особенного, но привлекает внимание людей и может быть легко найдено.

Для встроенного кода - хороший трюк от 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))

Мне нравятся макросы. Как весело при отладке!

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

#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.

Структурируйте литералы со значениями по умолчанию (отличными от нуля), используя переменные макросы C99

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. Это затенение с последующими упоминаниями того же члена четко определено в стандарте.

Можно упростить повторяющиеся вещи, т. е. списки перечислений

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  );

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

#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). Однако все возвращают строку, содержащую биты переданного значения.

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

Методы скобки полезны для этого. Открывающая скобка - это макрос, содержащий & Quot; try & Quot; и закрывающая скобка, содержащая набор " catch " es, перенос исключений в ErrorInfo и создание HRESULT.

Из проекта CrashRpt нужен трюк для расширения макросов и определения:

#define WIDEN2(x) L ## x 
#define WIDEN(x) WIDEN2(x)
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);

Большинство (всех?) платформ модульного тестирования C ++ построены на макросах. Мы используем UnitTest ++ . Проверьте это, чтобы увидеть все виды модных макросов.

BOOST_BINARY выполняет некоторые действия. устраните хитрость препроцессора, чтобы дать С ++ возможность выражать числовые константы в двоичном виде. Однако он ограничен 0-255.

макросы утилиты pthreads особенно впечатляют IMHO.

Когда я работаю с огромными вложенными структурами c / 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

Это облегчит жизнь во время обслуживания ... также во время разработки и кода будет читабельным.

Преобразование их в конструкцию языка для повышения безопасности типов и возможности отладки.

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);

На микроконтроллерах обычно выполняется отладка кода с использованием 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

Да, это грубо и небезопасно. Он применяется только до тех пор, пока ошибка не будет изолирована, поэтому этот макрос не причиняет вреда.

Часто я использую это. У меня есть заголовок 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!");
}

В макросах выполнять поток управления очень просто, потому что это просто подстановка текста. Вот пример с циклом 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;
} 
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top