调试宏可能会花费很多时间。我们好多了 避免它们,除非在非常罕见的情况下,既不常数, 函数或模板可以做我们想要的。
什么是罕见的情况?
答案 0 :(得分:34)
如果您想要实际的文本替换,那就是您使用宏的地方。看一下Boost.Preprocessor,这是在C ++ 03中模拟可变参数模板的好方法,而不会重复太多。
换句话说,如果您想 操纵程序代码本身 ,请使用宏。
另一个有用的应用程序是assert
,当NDEBUG
未定义时(通常是发布模式编译),它被定义为无操作。
这将我们带到下一点,这是第一点的特殊化:具有不同编译模式的不同代码,或者在不同编译器之间。如果您需要交叉编译器支持,则无法在没有宏的情况下逃脱。一般来看看Boost,它需要宏,因为它必须支持各种编译器的各种不足。
另一个重要的一点是,当您需要呼叫站点信息而不想让您的代码用户出错时。你无法通过一个函数自动获得它。
#define NEEDS_INFO() \
has_info(__FILE__, __LINE__, __func__)
适当声明has_info
(以及C ++ 11 / C99 __func__
or similar)。
答案 1 :(得分:11)
这个问题似乎没有明确的,封闭式的答案,所以我只举几个例子。
假设您要打印有关给定类型的信息。编译代码中不存在类型名称,因此它们不可能由语言本身表示(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);
我最喜欢的用途是调度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);
答案 2 :(得分:8)
因为这是一个开放式问题,这是我经常使用并且发现方便的技巧。
如果你想在一个像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
,但它可以重新用于任何独立功能。
答案 3 :(得分:7)
如果要将值用作标识符和值,则一个示例是token pasting。来自msdn链接:
#define paster( n ) printf_s( "token" #n " = %d", token##n )
int token9 = 9;
paster( 9 ); // => printf_s( "token9 = %d", token9 );
在c++ faq中也存在一些情况,尽管可能存在替代方案,但宏观解决方案是最好的方法。一个例子是pointers to member functions,右边是宏
#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
让打电话变得更容易,而不是处理所有尝试不用宏的头发。
int ans = CALL_MEMBER_FN(fred,p)('x', 3.14);
老实说,我只是接受他们的话,并且这样做,但显然随着电话变得更加复杂,它会变得更糟。
的示例答案 4 :(得分:5)
当您需要调用本身时,可以选择从函数返回。
#define MYMACRO(x) if(x) { return; }
void fn()
{
MYMACRO(a);
MYMACRO(b);
MYMACRO(c);
}
这通常用于少量重复代码。
答案 5 :(得分:4)
我不确定调试宏需要花费很多时间。我相信我发现宏的调试很简单(甚至100行怪物宏),因为你有可能看一下扩展(例如使用gcc -C -E
) - 这对于例如C ++模板。
C宏在多种情况下都很有用:
__LINE__
)查看主要免费软件中的#define
- d宏的许多用法(如Gtk,Gcc,Qt,...)
我很遗憾的是,C宏语言是如此有限....想象一下,如果C语言的语言能像Guile一样强大! (然后你可以把像flex
或bison
那样复杂的东西写成宏。)
看看Common Lisp宏的强大功能!
答案 6 :(得分:4)
如果您使用的是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; \
}
答案 7 :(得分:4)
考虑标准的assert
宏。
__FILE__
和__LINE__
宏来创建对源代码中位置的引用。答案 8 :(得分:4)
我曾经使用宏来生成一个大的字符串数组以及索引枚举:
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
当你有几个必须保持同步的数组时,这很有用。
答案 9 :(得分:2)
扩展@ 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;
}
如果您将宏连接到自动记录任何故障,这将是非常方便的:使用其他宏观想法,如令牌粘贴和文件, LINE 等;我甚至可以使日志条目包含代码位置和失败的表达式。如果你愿意的话,你也可以在那里抛出一个断言!
#define SUCCEED_OR_RETURN(expression) { \
HRESULT hrTest = (expression); \
if (!SUCCEEDED(hrTest)) { \
logFailure( \
#expression, \
HResultValueToString(hrTest), \
__FILE__, \
__LINE__, \
__FUNCTION__); \
return hrTest; \
} \
}
答案 10 :(得分:1)
调试会更容易,因为您的项目将分为每个任务的各个模块。当您拥有大型复杂的软件项目时,宏非常有用。但是有一些陷阱被陈述here。
答案 11 :(得分:0)
对我而言,将宏用于常量和没有分离逻辑功能的代码部分更为舒适。但是(内联)函数和(类函数)宏之间存在一些重要的区别,它们是: http://msdn.microsoft.com/en-us/library/bf6bf4cf.aspx