极少数情况下必须使用MACRO

时间:2011-12-14 18:29:48

标签: c++ c macros

  

调试宏可能会花费很多时间。我们好多了   避免它们,除非在非常罕见的情况下,既不常数,   函数或模板可以做我们想要的。

什么是罕见的情况?

12 个答案:

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

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

以下是someone trying to go it alone

的示例

答案 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一样强大! (然后你可以把像flexbison那样复杂的东西写成宏。)

看看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