Отладка макросов может занять много времени. Нам намного лучше избегая их, за исключением очень редких случаев, когда ни константы, функции и шаблоны могут делать то, что мы хотим.
Каковы редкие случаи?
Отладка макросов может занять много времени. Нам намного лучше избегая их, за исключением очень редких случаев, когда ни константы, функции и шаблоны могут делать то, что мы хотим.
Каковы редкие случаи?
Если вы хотите фактическую текстовую замену, то, где вы используете макросы. Взгляните на Boost.Preprocessor, это отличный способ имитировать вариационные шаблоны в С++ 03, не повторяя слишком много.
Другими словами, если вы хотите управлять самим программным кодом, используйте макросы.
Другим полезным приложением является assert
, который определяется как no-op, когда NDEBUG
не определен (обычно компиляция режима выпуска).
Это приводит нас к следующему пункту, который является специализацией первого: разный код с различными режимами компиляции или между разными компиляторами. Если вы хотите использовать кросс-компилятор, вы не сможете обойтись без макросов. Взгляните на Boost в целом, он все время нуждается в макросах из-за различных недостатков в различных компиляторах, которые он должен поддерживать.
Еще один важный момент - когда вам нужна информация о сайте-контакте, не требуя ошибки пользователя вашего кода. У вас нет возможности автоматически получить это с помощью только функции.
#define NEEDS_INFO() \
has_info(__FILE__, __LINE__, __func__)
С подходящим объявлением has_info
(и С++ 11/C99 __func__
или аналогичным).
У этого вопроса нет определенного ответа с закрытой формой, поэтому я просто приведу несколько примеров.
Предположим, вы хотите распечатать информацию о заданном типе. Имена типов не существуют в скомпилированном коде, поэтому они не могут быть выражены самим языком (кроме расширений С++). Здесь препроцессор должен выполнить следующие действия:
#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);
Мое любимое использование предназначено для отправки вызовов функций 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);
Так как это Открытый вопрос Вопрос трюк, который я часто использую и нахожу удобным.
Если вы хотите написать функцию-обертку над свободной функцией, например say 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
, он может быть повторно использован для любой функции свободного стояния.
Одним из примеров является прикрепление токенов, если вы хотите использовать значение как идентификатор, так и значение. Из ссылки msdn:
#define paster( n ) printf_s( "token" #n " = %d", token##n )
int token9 = 9;
paster( 9 ); // => printf_s( "token9 = %d", token9 );
Есть также случаи в С++ faq, хотя, возможно, есть альтернативы, решение макросов - лучший способ сделать что-то. Одним из примеров является указатели на функции-члены, где правый макрос
#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
упрощает вызов, а не обрабатывает все различные волосы, пытаясь сделать это с макросом.
int ans = CALL_MEMBER_FN(fred,p)('x', 3.14);
Честно говоря, я просто беру слово за это и делаю это так, но, видимо, это ухудшается, поскольку вызовы становятся более сложными.
Вот пример кто-то пытается сделать это самостоятельно
Если вам нужен сам вызов для необязательного возврата из функции.
#define MYMACRO(x) if(x) { return; }
void fn()
{
MYMACRO(a);
MYMACRO(b);
MYMACRO(c);
}
Обычно это используется для небольших битов повторяющегося кода.
Я не уверен, что отладка макросов занимает много времени. Я считаю, что я нахожу простую отладку макросов (даже 100 строк монстров-макросов), потому что у вас есть возможность взглянуть на расширение (например, с помощью gcc -C -E
), что менее возможно с помощью, например, Шаблоны С++.
C полезны, когда несколько раз:
__LINE__
)Посмотрите на многочисленные применения макросов #define
-d внутри основного свободного программного обеспечения (например, Gtk, Gcc, Qt,...)
Я очень сожалею о том, что язык макросов C настолько ограничен... Представьте, что макрос C был бы столь же мощным, как Guile!!! (Тогда вы могли бы написать такие сложные вещи, как flex
или bison
в качестве макросов).
Посмотрите на мощь общих макросов Lisp!
Если вы используете 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; \
}
Рассмотрим стандартный макрос assert
.
__FILE__
и __LINE__
для создания ссылок на местоположение в исходном коде.Я когда-то использовал макрос для создания большого массива строк вместе с перечислением индекса:
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
Это полезно, когда у вас есть несколько массивов, которые необходимо синхронизировать.
Чтобы расширить на @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;
}
С макро-y приятным способом:
// 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; \
} \
}
Отладка будет намного проще, так как ваш проект будет разделен на различные модули для каждой задачи. Макросы могут быть очень полезными, если у вас большой и сложный программный проект. Но есть некоторые подводные камни, которые указаны здесь.
Для меня удобнее использовать макросы для констант и для частей кода, которые не имеют разделенных логических функций. Но есть некоторые важные различия между (встроенными) функциями и (функциональными) макросами, вот они: http://msdn.microsoft.com/en-us/library/bf6bf4cf.aspx