使用#ifdef 而不是编写可移植代码,您仍然要编写多段特定于平台的代码。不幸的是,在许多(大多数?)情况下,您很快就会得到一个几乎难以理解的可移植代码和特定于平台的代码的混合体。
您还经常将#ifdef 用于可移植性以外的目的(定义要生成的代码的“版本”,例如将包括什么级别的自我诊断)。不幸的是,两者经常互动,并交织在一起。例如,将一些代码移植到 MacOS 的人认为它需要更好的错误报告,他补充道——但使其特定于 MacOS。后来,其他人认为更好的错误报告在 Windows 上非常有用,因此如果定义了 WIN32,他会通过自动#defineing MACOS 来启用该代码——但随后添加“只是更多”#ifdef WIN32 以排除一些真正是在定义 Win32 时特定于 MacOS 的代码。当然,我们还添加了 MacOS 基于 BSD Unix 的事实,因此在定义 MACOS 时,它也会自动定义 BSD_44 —— 但是(再次)转过身来排除一些 BSD“东西” " 为 MacOS 编译时。
这很快就会退化为如下示例的代码(取自#ifdef Considered Harmful):
#ifdef SYSLOG
#ifdef BSD_42
openlog("nntpxfer", LOG_PID);
#else
openlog("nntpxfer", LOG_PID, SYSLOG);
#endif
#endif
#ifdef DBM
if (dbminit(HISTORY_FILE) < 0)
{
#ifdef SYSLOG
syslog(LOG_ERR,"couldn’t open history file: %m");
#else
perror("nntpxfer: couldn’t open history file");
#endif
exit(1);
}
#endif
#ifdef NDBM
if ((db = dbm_open(HISTORY_FILE, O_RDONLY, 0)) == NULL)
{
#ifdef SYSLOG
syslog(LOG_ERR,"couldn’t open history file: %m");
#else
perror("nntpxfer: couldn’t open history file");
#endif
exit(1);
}
#endif
if ((server = get_tcp_conn(argv[1],"nntp")) < 0)
{
#ifdef SYSLOG
syslog(LOG_ERR,"could not open socket: %m");
#else
perror("nntpxfer: could not open socket");
#endif
exit(1);
}
if ((rd_fp = fdopen(server,"r")) == (FILE *) 0){
#ifdef SYSLOG
syslog(LOG_ERR,"could not fdopen socket: %m");
#else
perror("nntpxfer: could not fdopen socket");
#endif
exit(1);
}
#ifdef SYSLOG
syslog(LOG_DEBUG,"connected to nntp server at %s", argv[1]);
#endif
#ifdef DEBUG
printf("connected to nntp server at %s\n", argv[1]);
#endif
/*
* ok, at this point we’re connected to the nntp daemon
* at the distant host.
*/
这是一个相当小的例子,只涉及几个宏,但阅读代码已经很痛苦了。我个人看到(并且不得不处理)在实际代码中很多更糟。这里的代码很难看而且读起来很痛苦,但仍然很容易弄清楚在什么情况下将使用哪些代码。在许多情况下,您最终会得到更复杂的结构。
为了给出一个具体的例子来说明我希望如何写,我会做这样的事情:
if (!open_history(HISTORY_FILE)) {
logerr(LOG_ERR, "couldn't open history file");
exit(1);
}
if ((server = get_nntp_connection(server)) == NULL) {
logerr(LOG_ERR, "couldn't open socket");
exit(1);
}
logerr(LOG_DEBUG, "connected to server %s", argv[1]);
在这种情况下,可能我们对 loggerr 的定义将是一个宏而不是一个实际的函数。这可能是微不足道的,所以有一个像这样的标题是有意义的:
#ifdef SYSLOG
#define logerr(level, msg, ...) /* ... */
#else
enum {LOG_DEBUG, LOG_ERR};
#define logerr(level, msg, ...) /* ... */
#endif
[暂时,假设预处理器可以/将处理可变参数宏]
考虑到你的主管的态度,即使这样可能也是不可接受的。如果是这样,那很好。取而代之的是宏,而是在函数中实现该功能。将函数的每个实现隔离在其自己的源文件中,并构建适合目标的文件。如果您有很多特定于平台的代码,您通常希望将其隔离到它自己的目录中,很可能使用它自己的 makefile1,并有一个顶级的 makefile 来选择哪个基于指定目标调用的其他 makefile。
- 有些人不喜欢这样做。我并不是真的在争论如何构建 makefile,只是指出有些人认为/认为它很有用。