你所遇到的最糟糕的实际宏/预处理器滥用是什么(请不要回答显然的IOCCC*哈哈*)?
如果有一个有趣的小片段或故事,请添加进来。目标是教授一些东西,而不是总是告诉人们“永远不要使用宏”。
p.s .:我以前使用过宏……但通常我最终会摆脱它们,当我有了“真正的”解决方案时(即使真正的解决方案是内联的,因此类似于宏)。
奖励:给出一个示例,其中宏确实比非宏解决方案更好。
相关问题:C++宏何时有利?
你所遇到的最糟糕的实际宏/预处理器滥用是什么(请不要回答显然的IOCCC*哈哈*)?
如果有一个有趣的小片段或故事,请添加进来。目标是教授一些东西,而不是总是告诉人们“永远不要使用宏”。
p.s .:我以前使用过宏……但通常我最终会摆脱它们,当我有了“真正的”解决方案时(即使真正的解决方案是内联的,因此类似于宏)。
奖励:给出一个示例,其中宏确实比非宏解决方案更好。
相关问题:C++宏何时有利?
任何使用sendmail及其魔法配置语法的内容
#define "CR_LF" '\r'
那让我很困惑了一段时间!
#define ENUM_COLORS(CallbackMacro) \
CallbackMacro(RED) \
CallbackMacro(GREEN) \
CallbackMacro(BLUE) \
// ...
#define DEFINE_COLOR_TYPE_CALLBACK(Color) \
Color,
enum MyColorType {
ENUM_COLORS(DEFINE_COLOR_TYPE_CALLBACK)
};
void RegisterAllKnownColors(void)
{
#define REGISTER_COLOR_CALLBACK(Color) \
RegisterColor(Color, #Color);
ENUM_COLORS(REGISTER_COLOR_CALLBACK)
}
void RegisterColor(MyColorType Color, char *ColorName)
{
// ...
}
我同意大部分情况下,宏是很难使用的,但我发现有一些情况下它们非常有用。
在我看来,这个实际上非常出色,因为你只能通过sprintf获得类似的东西,然后需要资源分配等等,而且所有工作都完全由预处理器完成。
// Macro: Stringize
//
// Converts the parameter into a string
//
#define Stringize( L ) #L
// Macro: MakeString
//
// Converts the contents of a macro into a string
//
#define MakeString( L ) Stringize(L)
// Macro: $LINE
//
// Gets the line number as a string
//
#define $LINE MakeString( __LINE__ )
// Macro: $FILE_POS
//
// Gets the current file name and current line number in a format the Visual Studio
// can interpret and output goto
//
// NOTE: For VS to properly interpret this, it must be at the start of the line (can only have whitespace before)
//
#define $FILE_POS __FILE__ "(" $LINE ") : "
#define TEMPLATE_DEFS typename ReturnType
#define TEMPLATE_DECL ReturnType
#define FUNCTION_PARAMS void
#define FUNCTION_PASS
#define GENERIC_CALLBACK_DECL_NAME CallbackSafePointer0
#include "Callback.inl"
#define TEMPLATE_DEFS typename ReturnType, typename P1
#define TEMPLATE_DECL ReturnType, P1
#define FUNCTION_PARAMS P1 param1
#define FUNCTION_PASS param1
#define GENERIC_CALLBACK_DECL_NAME CallbackSafePointer1
#include "Callback.inl"
#define TEMPLATE_DEFS typename ReturnType, typename P1, typename P2
#define TEMPLATE_DECL ReturnType, P1, P2
#define FUNCTION_PARAMS P1 param1, P2 param2
#define FUNCTION_PASS param1, param2
#define GENERIC_CALLBACK_DECL_NAME CallbackSafePointer2
#include "Callback.inl"
#define TEMPLATE_DEFS typename ReturnType, typename P1, typename P2, typename P3
#define TEMPLATE_DECL ReturnType, P1, P2, P3
#define FUNCTION_PARAMS P1 param1, P2 param2, P3 param3
#define FUNCTION_PASS param1, param2, param3
#define GENERIC_CALLBACK_DECL_NAME CallbackSafePointer3
#include "Callback.inl"
// and so on...
虽然这使得阅读"Callback.inl"有点可怕,但它完全消除了使用不同数量参数重写相同代码的需要。我还应该提到,"Callback.inl"在文件末尾#undefs所有宏,因此,这些宏本身不会干扰任何其他代码,只是使"Callback.inl"写起来有点困难(不过阅读和调试并不太难)
我大约10年前使用的ASIC的驱动程序代码有很多部分看起来像:
int foo(state_t *state) {
int a, b, rval;
$
if (state->thing == whatever) {
$
do_whatever(state);
}
// more code
$
return rval;
}
#if DEBUG
#define $ dolog("%s %d", __FILE__, __LINE__);
#else
#define $
#endif
top.c
的源文件看起来像:#include <namechanged.h>
#include <foo.c>
#include <bar.c>
#include <baz.c>
果然,这是Makefile中唯一被引用的文件。每当你更改任何内容时,你都必须重新编译所有内容。这是为了“使代码更快”。
另一种“创造性”的预处理器使用方式,尽管它更多地涉及术语的使用而不是机制(这些机制非常平凡):
/***********************************************************************
* OS2 and PCDOS share a lot of common codes. However, sometimes
* OS2 needs codes similar to those of UNIX. NOTPCDOS is used in these
* situations
*/
#ifdef OS2
#define PCDOS
#define NOTPCDOS
#else /* OS2 */
#ifndef PCDOS
#define NOTPCDOS
#endif /* PCDOS */
#endif /* OS2 */
真正的代码 - 我以为我已经删除了它,但显然没有。我可能在某个临时分支中这样做了,并且没有得到将其检查回主要代码的权限。这是待办事项清单上的另一项。
在声明中发现了太多混淆:
NON_ZERO_BYTE Fixed(8) Constant('79'X),
IF WORK_AREA(INDEX) = ZERO_BYTE THEN /* found zero byte */
WORK_AREA(INDEX) = NON_ZERO_BYTE ; /* reset to nonzero*/
最糟糕的滥用(我有时也会犯这种错误)是将预处理器用作某种数据文件替换,例如:
#define FOO_RELATION \
BAR_TUPLE( A, B, C) \
BAR_TUPLE( X, Y, Z) \
然后在其他地方:
#define BAR_TUPLE( p1, p2, p3) if( p1 ) p2 = p3;
FOO_RELATION
#undef BAR_TUPLE
这将导致:
if( A ) B = C;
if( X ) Y = Z;
这个模式可以用来做各种(可怕的)事情...生成switch语句或巨大的if else块,或者与“真实”代码进行接口。你甚至可以使用它来::咳嗽::在非面向对象的上下文菜单系统中生成一个上下文菜单::咳嗽::。不过我从来不会做这么无聊的事情。
编辑:修复了括号不匹配的问题并扩展了示例
BSD内核中的NFS代码在宏之间使用goto。它仍在使用中,而且代码实际上是有效的。我知道有几个人试图清理它,但他们都放弃了一段时间——它太混乱了。
您可以在此处查看: http://www.openbsd.org/cgi-bin/cvsweb/src/sys/nfs/nfsm_subs.h?rev=1.43