极少数情况下必须使用MACRO

调试宏可能会花费很多时间。 我们最好避免使用它们,除非在非常罕见的情况下,常量,函数和模板都不能达到我们想要的效果。

什么是罕见的情况?

如果您想要实际的文本替换 ,那就是您使用宏的地方。 看看Boost.Preprocessor ,这是在C ++ 03中模拟可变参数模板的好方法,而不会重复太多。

换句话说,如果要操作程序代码本身 ,请使用宏。

另一个有用的应用程序是assert ,当未定义NDEBUG时(通常是释放模式编译),它被定义为无操作。

这将我们带到下一点,这是第一点的特殊化:具有不同编译模式的不同代码,或者在不同编译器之间。 如果您需要交叉编译器支持,则无法在没有宏的情况下离开。 一般来看Boost,它需要宏,因为它必须支持各种编译器的各种缺陷。

另一个重要的一点是,当您需要呼叫站点信息而不想让您的代码用户出错时。 你无法通过一个function自动获得它。

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

使用合适的has_info声明(和C ++ 11 / C99 __func__ 或类似声明 )。

这个问题似乎没有明确的,封闭式的答案,所以我只举几个例子。

假设您要打印有关给定类型的信息。 编译代码中不存在类型名称,因此它们不可能由语言本身表示(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); 

由于这是一个开放式结束问题,我经常使用并找到方便的技巧。

如果你想在像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但它可以真正重复用于任何独立function。

如果要将值用作标识符和值,则一个示例是标记粘贴 。 从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); 

老实说,我只是接受他们的话,并且这样做,但显然随着电话变得更加复杂,情况会变得更糟。

这是一个试图单独行动的人的例子

当您需要调用本身时,可以选择从函数返回。

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

这通常用于少量重复代码。

我不确定调试宏需要花费很多时间。 我相信我发现宏的调试很简单(甚至100行怪物宏),因为你有可能看到扩展(例如使用gcc -C -E ) – 这对于例如C ++模板来说是不太可能的。

在多种情况下,C宏很有用:

  • 你想以几种不同的方式处理事物清单
  • 你想定义一个“左值”表达式
  • 你需要效率
  • 你需要通过__LINE__获得宏的位置)
  • 你需要唯一的标识符
  • 等等

看看主要免费软件中的#define -d宏的许多用法(如Gtk,Gcc,Qt,……)

我很遗憾的是C宏语言是如此有限….想象一下,如果C语言语言能像Guile一样强大! (然后你可以像flex一样编写像flexbison这样复杂的东西)。

看看Common Lisp宏的强大function!

如果您使用的是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; } 

如果您连接宏以自动记录任何故障,使用其他宏观想法,如令牌粘贴和FILELINE等,这是非常方便的; 我甚至可以使日志条目包含代码位置和失败的表达式。 如果你愿意的话,你也可以在那里抛出一个断言!

 #define SUCCEED_OR_RETURN(expression) { \ HRESULT hrTest = (expression); \ if (!SUCCEEDED(hrTest)) { \ logFailure( \ #expression, \ HResultValueToString(hrTest), \ __FILE__, \ __LINE__, \ __FUNCTION__); \ return hrTest; \ } \ } 

调试将更容易,因为您的项目将分为每个任务的各个模块。 当您拥有大型复杂的软件项目时,宏非常有用。 但是这里有一些陷阱。

对我来说,将宏用于常量和没有分离逻辑function的代码部分更为舒适。 但是(内联)函数和(函数式)宏之间存在一些重要的区别,它们是: http : //msdn.microsoft.com/en-us/library/bf6bf4cf.aspx