你遇到的最糟糕的实际宏/预处理滥用是什么?

176

你所遇到的最糟糕的实际宏/预处理器滥用是什么(请不要回答显然的IOCCC*哈哈*)?

如果有一个有趣的小片段或故事,请添加进来。目标是教授一些东西,而不是总是告诉人们“永远不要使用宏”。


p.s .:我以前使用过宏……但通常我最终会摆脱它们,当我有了“真正的”解决方案时(即使真正的解决方案是内联的,因此类似于宏)。


奖励:给出一个示例,其中宏确实比非宏解决方案更好。

相关问题:C++宏何时有利?


+1 为引起人们对我在宏命令手中遭受的普遍虐待的关注。 - i_am_jorf
37
#define true false //开心调试 :) 该行代码的意思是将 true 宏定义为 false,在进行调试时会出现一些奇怪的结果,因此在代码后面加上注释“happy debugging :)”来缓解调试带来的压力。 - n0rd
社区维基意味着没有人会因为对这个问题或其答案的赞/踩而获得(或失去)声望。许多人认为这样的问题是轻松获取声望的廉价方式,因此如果将其标记为社区维基,人们就不太可能因此感到不满并关闭它。 - Graeme Perrow
2
人们很可能会因为一些幽默有趣的内容而感到不悦并将其关闭:您是在暗示您不希望在Stack Overflow上看到任何幽默/有趣的内容吗? - Trevor Boyd Smith
2
只是一个小观点,预处理器是语言的一部分,因此使用它并不邪恶/错误,就像其他任何东西一样。 - Mr. Boy
显示剩余3条评论
70个回答

3
我曾经见过最糟糕的情况是在我的当前项目中,有很多这样的情况:
#if PROGRAMA
     .
     .
    if(...)
    {
     .
     .
     .
#else
    .
     .
    if(...)
    {
     .
     .
     .
#endif
     }

是的, 他一次关闭了两个打开的窗口。


2
#define PROCESS_AND_RETURN(X) \
X.process(); \
// Important: Return only after invoking virtual method process() \
return X

由于“重要”注释,宏永远不会返回对象并崩溃!

2

我还要补充一个让我逐渐感到烦恼的问题:

#define ARRAYSIZE(x) (sizeof(x)/sizeof((x)[0]))

如果他们做对了的话,我见过所有可能排列的括号版本。我还见过它在同一个头文件中被定义了两次。

主要是针对Windows(虽然我认为其他操作系统SDK也有类似的东西),几乎每个人都似乎觉得需要在项目的头文件中定义这个宏,我不明白为什么。

WinNT.h(由Windows.h包含)定义了一个非常好的版本,如果你传递指针类型而不是数组,则执行一些模板魔术使其产生编译时错误。

当然,如果您正在构建C程序,它会回退到我上面写的内容,但我仍然不会无缘无故地重新定义SDK默认具有的东西。


我发现 #define DIM(x) (sizeof x / sizeof * x) 让我省了很多打字的时间。 - Jamie
2
我对这个想法没有问题,只要使用您的SDK附带的那个就可以了。如果您想节省打字时间,可以尝试使用 #define DIM(x) ARRAYSIZE(x)。 - i_am_jorf
仅为了这个宏而拖入整个 <windows.h> 似乎有些过度。您还将费尽心思编写不可移植的代码。 - Chris Oldwood
1
如果你在Windows上编写代码,那么你已经在引用windows.h了。除非你只是写一些小的控制台应用之类的。我的意思是,你可以尝试坚持使用crt函数,但最终你会想做一些需要win32的事情。 - i_am_jorf
我就是不明白为什么你不能用一个函数代替。此外,与宏不同,使用函数更加安全,因为它不会与指针产生冲突。 - Paul Fultz II

2
我曾经拼凑出一段可怕的C ++代码,使用宏将函数挂钩到DLL的导入表中。

#define ARGLIST(...) __VA_ARGS__

#define CPPTYPELESSARG(typelessParams) thisptr, typelessParams
#define CPPTYPEDARG(typedParams) void* thisptr, typedParams
#define CPPTYPELESSNOARG thisptr
#define CPPTYPEDNOARG void* thisptr

#define CPPHOOKBODY(hookName, params) void *thisptr; \
    __asm { mov thisptr, ecx } \
    return On##hookName ( params );


#define CHOOKBODY(hookName, typelessParams) return On##hookName( typelessParams );

#define CPPHOOK(InjectHookRef, importLib, importFunc, hookName, returnType, typedParams, typelessParams) \
    HOOKIMPL(InjectHookRef, importLib, importFunc, hookName, returnType, CPPTYPEDARG(typedParams), typelessParams, \
    typedParams, __thiscall, __stdcall, CPPHOOKBODY(hookName, CPPTYPELESSARG(typelessParams)))

#define CPPHOOKNOARG(InjectHookRef, importLib, importFunc, hookName, returnType, typedParams, typelessParams) \
    HOOKIMPL(InjectHookRef, importLib, importFunc, hookName, returnType, CPPTYPEDNOARG, typelessParams, \
    typedParams, __thiscall, __stdcall, CPPHOOKBODY(hookName, CPPTYPELESSNOARG))

#define CDECLHOOK(InjectHookRef, importLib, importFunc, hookName, returnType, typedParams, typelessParams) \
    HOOKIMPL(InjectHookRef, importLib, importFunc, hookName, returnType, typedParams, typelessParams, \
    typedParams, __cdecl, __cdecl, CHOOKBODY(hookName, typelessParams))

#define CDECLFUNC(name, address, returnType, args) \
    typedef returnType (__cdecl *name##Ptr)(args); \
    name##Ptr name = (name##Ptr) address;

#define CPPFUNC(name, address, returnType, args) \
    typedef returnType (__thiscall *name##Ptr)(void* thisptr, args); \
    name##Ptr name = (name##Ptr) address;

#define STDFUNC(name, address, returnType, args) \
    typedef returnType (__stdcall *name##Ptr)(args); \
    name##Ptr name = (name##Ptr) address;

#define STDHOOK(InjectHookRef, importLib, importFunc, hookName, returnType, typedParams, typelessParams) \
    HOOKIMPL(InjectHookRef, importLib, importFunc, hookName, returnType, typedParams, typelessParams, \
    typedParams, __stdcall, __stdcall, CHOOKBODY(hookName, ARGLIST(typelessParams)))

#define HOOKIMPL(InjectHookRef, importLib, importFunc, hookName, returnType, typedParams, typelessParams, hookParams, fnPtrCall, hookCall, hookBody) \
        typedef returnType (fnPtrCall *##hookName##OrigPtr )( typedParams ); \
        class hookName : public IHook \
        { \
        public: \
            typedef hookName##OrigPtr func_type; \
        private: \
            static void* m_origFunction; \
            static bool m_bModifyImport; \
            static std::string m_lib; \
            static std::string m_importFunc; \
            static std::string m_sHookName; \
            static returnType hookCall hookName##FnHook ( hookParams ) \
            { \
                hookBody \
            } \
            static bool ImplIsModifyImport() { return hookName::m_bModifyImport; } \
            static void ImplSetModifyImport(bool bModify) { hookName::m_bModifyImport = bModify; } \
            static const std::string& ImplGetLibName() { return hookName::m_lib; } \
            static const std::string& ImplGetImportFunctionName() { return hookName::m_importFunc; } \
            static void ImplSetOriginalAddress(void* fn) { hookName::m_origFunction = fn; } \
            static void* ImplGetOriginalAddress() { return hookName::m_origFunction; } \
            static returnType On##hookName ( typedParams ); \
            static void* ImplGetNewAddress() { return hookName::##hookName##FnHook; } \
            static const std::string& ImplGetHookName() { return hookName::m_sHookName; } \
        public: \
            hookName() \
            { \
                InjectHookRef.AddHook((IHook*)this); \
                hookName::m_lib = importLib; \
                hookName::m_importFunc = importFunc; \
                hookName::m_sHookName = #hookName; \
                hookName::m_origFunction = NULL; \
                hookName::m_bModifyImport = true; \
            } \
            virtual bool IsModifyImport() const { return hookName::ImplIsModifyImport(); } \
            virtual void SetModifyImport(bool bModify) { hookName::ImplSetModifyImport(bModify); } \
            virtual const std::string& GetHookName() const { return hookName::ImplGetHookName(); } \
            virtual const std::string& GetLibName() const { return hookName::ImplGetLibName(); } \
            virtual const std::string& GetImportFunctionName() const { return hookName::ImplGetImportFunctionName(); } \
            virtual void* GetOriginalAddress() const { return hookName::ImplGetOriginalAddress(); } \
            virtual void* GetNewAddress() const { return hookName::ImplGetNewAddress(); } \
            virtual void SetOriginalAddress(void* fn) { hookName::m_origFunction = fn; } \
            static func_type GetTypedOriginalAddress() { return reinterpret_cast(hookName::m_origFunction); } \
        }; \
        void* hookName::m_origFunction = NULL; \
        bool hookName::m_bModifyImport = false; \
        std::string hookName::m_lib; \
        std::string hookName::m_importFunc; \
        std::string hookName::m_sHookName; \
        static hookName g##hookName##Inst;

这反过来使我能够做到这一点:

CPPHOOK(gIH, "SimEngine.dll", "?AddEntity@Player@@UAEXPAVEntity@@@Z", PlayerAddEntity, void, void* ent, ent);

/* 当引擎调用Player::AddEntity(entity)时调用 */ void PlayerAddEntity::OnPlayerAddEntity(void *thisptr, void *ent) { unsigned int id = getPlayerID(thisptr);

gIH.GetLog()->Info("Player %d adding entity %s.", 
    getPlayerID(thisptr), getEntityName(ent));

gPlayers[id] = thisptr;

/*if( id == 2 && gPlayers[1] && gPlayers[2] )
    EntitySetOwner::GetTypedOriginalAddress() (ent, gPlayers[1]);*/
//gEnts[ent] = Entity(ent, Vector3f());

PlayerAddEntity::GetTypedOriginalAddress() (thisptr, ent);

}


2
这段话来自一个流行的开源程序。实际上,它通过隐藏丑陋的遗留代码,使一些代码部分更易读。
#define EP_STATUS    CASTLING][(BOARD_FILES-2)
#define HOLDINGS_SET CASTLING][(BOARD_FILES-1)

我想这里并没有什么真正的坏处,我只是觉得很有趣。 http://git.savannah.gnu.org/cgit/xboard.git/tree/common.h

2

当我第一次接触C语言中的宏时,我被搞糊涂了好几天。下面是我面临的情况。我想对于C专家来说,这很容易理解并且非常高效,然而对于我来说,要弄清楚到底发生了什么,就意味着需要将所有不同的宏粘贴在一起,直到整个函数可以被查看。这难道不是一个好习惯吗?!为什么不能使用普通的函数呢?!

#define AST_LIST_MOVE_CURRENT(newhead, field) do { \
typeof ((newhead)->first) __list_cur = __new_prev; \
AST_LIST_REMOVE_CURRENT(field); \
AST_LIST_INSERT_TAIL((newhead), __list_cur, field); \
} while (0) 

2

好的宏定义:(虽然我个人不喜欢使用这种语法所需的双括号;我更喜欢可变参数宏(仅限C99)或类似于PRINTF_0,PRINTF_1等的东西,取决于参数数量)

#ifdef DEBUG
#define PRINTF(x) printf x
#else
#define PRINTF(x)
#endif

对于非调试版本,它可以减少代码大小/执行时间(前者比后者更多)。此外,它还可以防止泄漏调试文本字符串,这可能会带来一些安全风险。


已更正。请用 \ 转义下划线 _。 - strager
我喜欢 #define D(s) do{s;}while(0),它被用作 D(printf(...));额外的括号很有意义... - RBerteig
如果开启调试模式,您也可以将PRINTF定义为"printf";如果未开启调试模式,则可以将其定义为"(void)"。然后它就能够与任意数量的参数一起使用,就像"(void)(1,2,3,4)"这样不执行任何操作的代码一样。 - Marten
@Marten:除非像“(void)(LookupDebugId(id), ConvertToString(id))”这样的东西,否则“ConvertToString”仍将被调用,只是其返回值被忽略,而“LookupDebugId”可能不存在。 如果你在Visual Studio环境中,你可以使用__noop,它可以实现你想要的功能。 - Simon Buchan
还要记得,如果x中有任何百分号,printf(x)可能会产生奇怪且潜在的灾难性结果。 - David Thornley

2

一位前雇主发现现代Unix系统上没有实现BASIC-PLUS,因此他们尝试使用C预处理器宏重新实现它:

#define IF if(
#define THEN ) {
#define ENDIF }
#define GOTO goto L

可怕。

...etc.

(请提供更多需要翻译的内容)

我曾经参与过一个代码库的工作,该代码库是从Pascal翻译成C的,试图使用这种方法保留Pascal语法。 - Chris Oldwood

2

请查看这个答案,了解一位患有阅读障碍的同事如何通过一个常用的头文件来简化生活,其中包含了像#define fasle false这样的内容。


2

与雷蒙德的发泄有关的是以下可怕的宏(在我看来,当然):

#define CALL_AND_CHECK(func, arg) \
    int result = func(arg);       \
    if(0 != result)               \
    {                             \
        sys.exit(-1);             \
    }                             \

我对使用宏的实践还比较新,使用了这个宏,但是我预期传递给它的函数会失败。而且我在后台线程中执行它,所以我的整个应用程序为什么会“崩溃”困扰了我好几天。

顺便说一下,如果当时有std::tr1::function,我就可以省下一周的时间了!


请注意,在同一作用域中两次使用CALL_AND_CHECK()将导致多次定义“result”,即使没有这样做,if (x) CALL_AND_CHECK(foo, y) else CALL_AND_CHECK(foo, z)也会产生令人惊讶的结果。 - David Thornley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接