必须使用宏的极少数情况
Rare cases where MACROs must be used
调试宏可能需要大量时间。我们过得好多了 避免使用它们,除非在极少数情况下两者都没有常量, 函数和模板都不能做我们想做的事。
有哪些罕见情况?
如果你想要实际的文本替换,那就是你使用宏的地方。看看 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
- 在没有太多条件句的情况下,我如何避免被零除
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 函数中堆分配的效果与缺少堆分配的情况
- 在未初始化映射的情况下,将值插入到映射的映射中
- 是默认情况下分配给char数组常量的值
- 为什么我不能在不创建字符串变量的情况下使用函数的字符串输出
- 如何在不产生任何垃圾的情况下获得C中的像素
- 在已经使用Git的情况下减少编译时间
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- 如何在没有信号的情况下从C++执行QML插槽
- 如何在不知道向量大小的情况下输入向量内部的向量?
- 如何针对特定情况调试和修复此双自由内存损坏问题
- 为什么在某些情况下不写入此文件?
- 为什么Mat类的两个对象可以在不重载运算符+的情况下添加
- 在没有Xcode的情况下在Mac捆绑包中嵌入框架
- 有没有一种方法可以测量c++程序的运行时内存使用情况
- UE4-如何在给定4个屏幕坐标的情况下缩放纹理或材质
- C++ strcpy 函数在少数主要情况下失败
- 为什么在pthread_detach()之后调用pthread_exit()时,在极少数情况下会导致SEGV
- 必须使用宏的极少数情况