Редкие случаи, когда необходимо использовать MACRO - PullRequest
26 голосов
/ 14 декабря 2011

Отладка макросов может занять много времени. Нам намного лучше избегая их, за исключением очень редких случаев, когда ни константы, функции или шаблоны могут делать то, что мы хотим.

Какие редкие случаи?

Ответы [ 12 ]

34 голосов
/ 14 декабря 2011

Если вы хотите фактическую текстовую замену , то здесь вы используете макросы. Взгляните на Boost.Preprocessor , это отличный способ симулировать вариационные шаблоны в C ++ 03, не повторяя себя слишком много.

Другими словами, если вы хотите манипулировать самим кодом программы , используйте макросы.

Другим полезным приложением является assert, которое определено как запрещающее, когда NDEBUG не определено (обычно компиляция в режиме выпуска).

Это подводит нас к следующему пункту, который является специализацией первого: разный код с разными режимами компиляции или между разными компиляторами. Если вам нужна поддержка кросс-компиляции, вам не обойтись без макросов. Взгляните на Boost в целом, ему все время нужны макросы из-за различных недостатков в различных компиляторах, которые он должен поддерживать.

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

#define NEEDS_INFO() \
  has_info(__FILE__, __LINE__, __func__)

С подходящим объявлением has_info (и C ++ 11 / C99 __func__ или аналогичным ).

11 голосов
/ 14 декабря 2011

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

Предположим, вы хотите напечатать информацию о данном типе.Имена типов не существуют в скомпилированном коде, поэтому они не могут быть выражены самим языком (за исключением расширений C ++).Здесь препроцессор должен вмешаться:

#define PRINT_TYPE_INFO(type) do { printf("sizeof(" #type ") = %zu\n", sizeof(type)); } while (false)

PRINT_TYPE_INFO(int);
PRINT_TYPE_INFO(double);

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

#define DECLARE_SYM(name) fhandle libfoo_##name = dlsym("foo_" #name, lib);

DECLARE_SYM(init);   // looks up "foo_init()", declares "libfoo_init" pointer
DECLARE_SYM(free);
DECLARE_SYM(get);
DECLARE_SYM(set);

Myлюбимое использование для отправки вызовов функций CUDA и проверки их возвращаемого значения:

#define CUDACALL(F, ARGS...) do { e = F(ARGS); if (e != cudaSuccess) throw cudaException(#F, e); } while (false)

CUDACALL(cudaMemcpy, data, dp, s, cudaMemcpyDeviceToHost);
CUDACALL(cudaFree, dp);
8 голосов
/ 14 декабря 2011

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

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

#define malloc(X) my_malloc( X, __FILE__, __LINE__, __FUNCTION__)

void* my_malloc(size_t size, const char *file, int line, const char *func)
{

    void *p = malloc(size);
    printf ("Allocated = %s, %i, %s, %p[%li]\n", file, line, func, p, size);

    /*Link List functionality goes in here*/

    return p;
}

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

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

7 голосов
/ 14 декабря 2011

Один пример - вставка токена , если вы хотите использовать значение как идентификатор, так и значение. Из ссылки MSDN:

#define paster( n ) printf_s( "token" #n " = %d", token##n )
int token9 = 9;

paster( 9 ); // => printf_s( "token9 = %d", token9 );

Существуют также случаи в c ++ faq , где, хотя могут быть и альтернативы, макро-решение - лучший способ сделать что-то. Одним из примеров является указатели на функции-члены , где правильный макрос

 #define CALL_MEMBER_FN(object,ptrToMember)  ((object).*(ptrToMember)) 

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

int ans = CALL_MEMBER_FN(fred,p)('x', 3.14);

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

Вот пример кто-то пытается сделать это один

5 голосов
/ 14 декабря 2011

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

#define MYMACRO(x) if(x) { return; }
void fn()
{
    MYMACRO(a);
    MYMACRO(b);
    MYMACRO(c);
}

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

4 голосов
/ 15 декабря 2011

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

strings.inc

GEN_ARRAY(a)
GEN_ARRAY(aa)
GEN_ARRAY(abc)
GEN_ARRAY(abcd)
// ...

strings.h

// the actual strings
#define GEN_ARRAY(x) #x ,
const char *strings[]={
    #include "strings.inc"
    ""
};
#undef GEN_ARRAY

// indexes
#define GEN_ARRAY(x) enm_##x ,
enum ENM_string_Index{
    #include "strings.inc"
    enm_TOTAL
};
#undef GEN_ARRAY

Это полезно, когда у вас есть несколько массивов, которые нужно синхронизировать.

4 голосов
/ 14 декабря 2011

Рассмотрим стандартный assert макрос.

  • Он использует условную компиляцию, чтобы гарантировать, что код включен только в отладочные сборки (вместо того, чтобы полагаться на оптимизатор для его исключения).
  • Он использует макросы __FILE__ и __LINE__ для создания ссылок на расположение в исходном коде.
4 голосов
/ 14 декабря 2011

Если вы используете C, вам нужно использовать макросы для имитации шаблонов.

С http://www.flipcode.com/archives/Faking_Templates_In_C.shtml

#define CREATE_VECTOR_TYPE_H(type) \
typedef struct _##type##_Vector{ \
  type *pArray; \
  type illegal; \
  int size; \
  int len; \
} type##_Vector; \
void type##_InitVector(type##_Vector *pV, type illegal); \
void type##_InitVectorEx(type##_Vector *pV, int size, type illegal); \
void type##_ClearVector(type##_Vector *pV); \
void type##_DeleteAll(type##_Vector *pV); \
void type##_EraseVector(type##_Vector *pV); \
int type##_AddElem(type##_Vector *pV, type Data); \
type type##_SetElemAt(type##_Vector *pV, int pos, type data); \
type type##_GetElemAt(type##_Vector *pV, int pos);

#define CREATE_VECTOR_TYPE_C(type) \
void type##_InitVector(type##_Vector *pV, type illegal) \
{ \
  type##_InitVectorEx(pV, DEF_SIZE, illegal); \
} \
void type##_InitVectorEx(type##_Vector *pV, int size, type illegal) \
{ \
  pV-len = 0; \
  pV-illegal = illegal; \
  pV-pArray = malloc(sizeof(type) * size); \
  pV-size = size; \
} \
void type##_ClearVector(type##_Vector *pV) \
{ \
  memset(pV-pArray, 0, sizeof(type) * pV-size); \
  pV-len = 0; \
} \
void type##_EraseVector(type##_Vector *pV) \
{ \
  if(pV-pArray != NULL) \
    free(pV-pArray); \
  pV-len = 0; \
  pV-size = 0; \
  pV-pArray = NULL; \
} \
int type##_AddElem(type##_Vector *pV, type Data) \
{ \
  type *pTmp; \
  if(pV-len = pV-size) \
  { \
    pTmp = malloc(sizeof(type) * pV-size * 2); \
    if(pTmp == NULL) \
      return -1; \
    memcpy(pTmp, pV-pArray, sizeof(type) * pV-size); \
    free(pV-pArray); \
    pV-pArray = pTmp; \
    pV-size *= 2; \
  } \
  pV-pArray[pV-len] = Data; \
  return pV-len++; \
} \
type type##_SetElemAt(type##_Vector *pV, int pos, type data) \
{ \
  type old = pV-illegal; \
  if(pos = 0 && pos <= pV-len) \
  { \
    old = pV-pArray[pos]; \
    pV-pArray[pos] = data; \
  } \
  return old; \
} \
type type##_GetElemAt(type##_Vector *pV, int pos) \
{ \
  if(pos = 0 && pos <= pV-len) \
    return pV-pArray[pos]; \
  return pV-illegal; \
} 
4 голосов
/ 14 декабря 2011

Я не уверен, что отладка макросов занимает много времени. Я полагаю, что я нахожу простым отладку макросов (даже 100-строчных монстров), потому что у вас есть возможность посмотреть на расширение (например, с использованием gcc -C -E) - что менее возможно с, например Шаблоны C ++.

Макросы

C полезны в следующих случаях:

  • вы хотите обрабатывать список несколькими разными способами
  • вы хотите определить выражение "lvalue"
  • вам нужна эффективность
  • вам нужно иметь расположение макроса через __LINE__)
  • вам нужны уникальные идентификаторы
  • и т. Д.

Посмотрите на множество применений макросов #define -d внутри основных бесплатных программных компонентов (таких как Gtk, Gcc, Qt, ...)

Что я очень сожалею, так это то, что язык макросов C настолько ограничен ... Представьте себе, если бы язык макросов C был бы таким же мощным, как Guile !!! (Тогда вы могли бы написать такие сложные вещи, как flex или bison как макросы).

Посмотрите на силу макросов Common Lisp!

2 голосов
/ 21 декабря 2011

Чтобы развернуть ответ @ tenfour об условных возвратах: я часто делаю это, когда пишу код Win32 / COM, где, кажется, я проверяю HRESULT каждую вторую строку. Например, сравните раздражающий способ:

// Annoying way:
HRESULT foo() {
    HRESULT hr = SomeCOMCall();
    if (SUCCEEDED(hr)) {
        hr = SomeOtherCOMCall();
    }

    if (SUCCEEDED(hr)) {
        hr = SomeOtherCOMCall2();
    }

    // ... ad nauseam.

    return hr;
}

С макро-у приятным способом:

// Nice way:
HRESULT foo() {
    SUCCEED_OR_RETURN(SomeCOMCall());
    SUCCEED_OR_RETURN(SomeOtherCOMCall());
    SUCCEED_OR_RETURN(SomeOtherCOMCall2());

    // ... ad nauseam.

    // If control makes it here, nothing failed.
    return S_OK;
}

Вдвойне удобно, если вы подключите макрос для автоматической регистрации любых сбоев: используйте другие идеи макросов, такие как вставка токена и FILE , LINE и т. Д .; Я даже могу сделать запись в журнале, содержащую местоположение кода и выражение, которое не удалось. Вы также можете добавить туда утверждение, если хотите!

#define SUCCEED_OR_RETURN(expression) { \
    HRESULT hrTest = (expression); \
    if (!SUCCEEDED(hrTest)) { \
        logFailure( \
            #expression, \
            HResultValueToString(hrTest), \
            __FILE__, \
            __LINE__, \
            __FUNCTION__); \
        return hrTest; \
    } \
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...