必须使用宏的极少数情况

Rare cases where MACROs must be used

本文关键字:极少数 情况      更新时间:2023-10-16

调试宏可能需要大量时间。我们过得好多了 避免使用它们,除非在极少数情况下两者都没有常量, 函数和模板都不能做我们想做的事。

有哪些罕见情况?

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

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

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

这就把我们带到了下一点,这是第一点的专业化:具有不同编译模式的不同代码,或不同编译器之间的代码。如果你想要交叉编译器支持,你不能离开宏。看看 Boost,它一直需要宏,因为它必须支持的各种编译器存在各种缺陷。

另一个重要的一点是,当您需要调用站点信息而不想窃听代码的用户时。你没有办法只用一个函数自动获得它。

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

并附有适当的has_info声明(以及 C++11/C99 __func__或类似规定(。

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

假设您要打印有关给定类型的信息。类型名称在编译的代码中不存在,因此它们不可能由语言本身表示(C++扩展除外(。在这里,预处理器必须介入:

#define PRINT_TYPE_INFO(type) do { printf("sizeof(" #type ") = %zun", 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但它实际上可以重用于任何独立的功能。

例如,

如果要将值同时用作标识符和值,则使用令牌粘贴。从 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一样复杂的东西,或者像宏一样bison的东西(。

看看Common 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__宏来创建对源代码中位置的引用。

我曾经使用宏来生成一个大型字符串数组以及索引枚举:

字符串.inc

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

字符串.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;
}

用宏的好方法:

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