macos 在代码中使用 #ifdef 是一种不好的做法吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8010234/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it a bad practice to use #ifdef in code?
提问by RLT
I have to use lot of #ifdef i386and x86_64for architecture specific code and some times #ifdef MAC or #ifdef WIN32... so on for platform specific code.
我必须使用大量#ifdef i386和x86_64用于特定于架构的代码,有时使用 #ifdef MAC 或 #ifdef WIN32... 等等用于特定于平台的代码。
We have to keep the common code base and portable.
我们必须保持通用代码库和可移植性。
But we have to follow the guideline that use of #ifdef is strict no. I dont understand why?
但是我们必须遵循#ifdef 的使用是严格不的准则。我不明白为什么?
As a extension to this question I would also like to understand when to use #ifdef ?
作为这个问题的扩展,我还想了解何时使用 #ifdef ?
For example, dlopen() cannot open 32 bit binary while running from 64 bit process and vice versa. Thus its more architecture specific. Can we use #ifdef in such situation?
例如,dlopen() 在从 64 位进程运行时无法打开 32 位二进制文件,反之亦然。因此它更具体的架构。我们可以在这种情况下使用 #ifdef 吗?
采纳答案by Jerry Coffin
With #ifdef
instead of writing portable code, you're still writing multiple pieces of platform-specific code. Unfortunately, in many (most?) cases, you quickly end up with a nearly impenetrable mixture of portable and platform-specific code.
与#ifdef
编写可移植代码不同,您仍在编写多段特定于平台的代码。不幸的是,在许多(大多数?)情况下,您很快就会得到一个几乎难以理解的可移植代码和特定于平台的代码的混合物。
You also frequently get #ifdef
being used for purposes other than portability (defining what "version" of the code to produce, such as what level of self-diagnostics will be included). Unfortunately, the two often interact, and get intertwined. For example, somebody porting some code to MacOS decides that it needs better error reporting, which he adds -- but makes it specific to MacOS. Later, somebody else decides that the better error reporting would be awfully useful on Windows, so he enables that code by automatically #define
ing MACOS if WIN32 is defined -- but then adds "just a couple more" #ifdef WIN32
to exclude some code that really isMacOS specific when Win32 is defined. Of course, we also add in the fact that MacOS is based on BSD Unix, so when MACOS is defined, it automatically defines BSD_44 as well -- but (again) turns around and excludessome BSD "stuff" when compiling for MacOS.
您还经常#ifdef
被用于可移植性以外的目的(定义要生成的代码的“版本”,例如将包含的自我诊断级别)。不幸的是,两者经常互动,并交织在一起。例如,有人将一些代码移植到 MacOS 上,他决定需要更好的错误报告,他补充道——但使其特定于 MacOS。后来,别人决定了更好的错误报告将在Windows非常有用的,所以他能够代码自动#define
荷兰国际集团MACOS如果WIN32定义-但随后补充道“只是一对夫妇更”#ifdef WIN32
排除一些代码,确实是定义 Win32 时特定于 MacOS。当然,我们还添加了一个事实,即 MacOS 基于 BSD Unix,因此当定义 MACOS 时,它也会自动定义 BSD_44——但是(再次)在为 MacOS 编译时反过来并排除一些 BSD “东西”。
This quickly degenerates into code like the following example (taken from #ifdef Considered Harmful):
这很快就会退化为如下示例的代码(取自#ifdef 考虑有害):
#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.
*/
This is a fairly small example with only a fewmacros involved, yet reading the code is already painful. I've personally seen (and had to deal with) muchworse in real code. Here the code is ugly and painful to read, but it's still fairly easy to figure out which code will be used under what circumstances. In many cases, you end up with much more complex structures.
这是一个相当小的例子,只涉及几个宏,但阅读代码已经很痛苦了。我个人在实际代码中看到(并且不得不处理)更糟糕的情况。这里的代码很难看而且读起来很痛苦,但是弄清楚在什么情况下会使用哪些代码仍然相当容易。在许多情况下,您最终会得到更复杂的结构。
To give a concrete example of how I'd prefer to see that written, I'd do something like this:
举一个具体的例子来说明我希望看到的书面内容,我会做这样的事情:
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]);
In such a case, it's possiblethat our definition of logerr would be a macro instead of an actual function. It might be sufficiently trivial that it would make sense to have a header with something like:
在这种情况下,这是可能的,我们LOGERR的定义是一个宏,而不是实际的功能。有一个像这样的标题可能是有意义的:
#ifdef SYSLOG
#define logerr(level, msg, ...) /* ... */
#else
enum {LOG_DEBUG, LOG_ERR};
#define logerr(level, msg, ...) /* ... */
#endif
[for the moment, assuming a preprocessor that can/will handle variadic macros]
[目前,假设预处理器可以/将处理可变参数宏]
Given your supervisor's attitude, even that maynot be acceptable. If so, that's fine. Instead a macro, implement that capability in a function instead. Isolate each implementation of the function(s) in its own source file and build the files appropriate to the target. If you have a lot of platform-specific code, you usually want to isolate it into a directory of its own, quite possibly with its own makefile1, and have a top-level makefile that just picks which other makefiles to invoke based on the specified target.
考虑到您的主管的态度,即使这样也可能无法接受。如果是这样,那很好。而不是宏,而是在函数中实现该功能。将函数的每个实现隔离在其自己的源文件中,并构建适合目标的文件。如果您有很多特定于平台的代码,您通常希望将它隔离到一个自己的目录中,很可能有自己的 makefile 1,并且有一个顶级的 makefile,它只是根据指定目标。
- Some people prefer not to do this. I'm not really arguing one way or the other about how to structure makefiles, just noting that it's a possibility some people find/consider useful.
- 有些人不愿意这样做。我并不是真的在争论如何构建 makefile 的一种或另一种方式,只是注意到有些人发现/认为这是有用的可能性。
回答by Dima
You should avoid #ifdef
whenever possible. IIRC, it was Scott Meyers who wrote that with #ifdef
s you do not get platform-independent code. Instead you get code that depends on multiple platforms. Also #define
and #ifdef
are not part of the language itself. #define
s have no notion of scope, which can cause all sorts of problems. The best way is to keep the use of the preprocessor to a bare minimum, such as the include guards. Otherwise you are likely to end up with a tangled mess, which is very hard to understand, maintain, and debug.
你应该#ifdef
尽可能避免。IIRC,是 Scott Meyers 写的,用#ifdef
s 你不会得到与平台无关的代码。相反,您会获得依赖于多个平台的代码。也#define
和#ifdef
不是语言本身的一部分。 #define
s 没有范围的概念,这会导致各种问题。最好的方法是将预处理器的使用保持在最低限度,例如包含守卫。否则你很可能最终会陷入混乱,这很难理解、维护和调试。
Ideally, if you need to have platform-specific declarations, you should have separate platform-specific include directories, and handle them appropriately in your build environment.
理想情况下,如果您需要特定于平台的声明,您应该有单独的特定于平台的包含目录,并在您的构建环境中适当地处理它们。
If you have platform specific implementation of certain functions, you should also put them into separate .cpp files and again hash them out in the build configuration.
如果您有特定功能的平台特定实现,您还应该将它们放入单独的 .cpp 文件中,并在构建配置中再次散列它们。
Another possibility is to use templates. You can represent your platforms with empty dummy structs, and use those as template parameters. Then you can use template specialization for platform-specific code. This way you would be relying on the compiler to generate platform-specific code from templates.
另一种可能性是使用模板。您可以使用空的虚拟结构来表示您的平台,并将它们用作模板参数。然后您可以对特定于平台的代码使用模板特化。通过这种方式,您将依赖编译器从模板生成特定于平台的代码。
Of course, the only way for any of this to work, is to very cleanly factor out platform-specific code into separate functions or classes.
当然,其中任何一个工作的唯一方法是非常干净地将特定于平台的代码分解为单独的函数或类。
回答by Matthieu M.
I have seen 3 broad usages of #ifdef
:
我见过 3 种广泛的用法#ifdef
:
- isolate platform specific code
- isolate feature specific code (not all versions of a compilers / dialect of a language are born equal)
- isolate compilation mode code (
NDEBUG
anyone ?)
- 隔离平台特定代码
- 隔离特定于功能的代码(并非所有版本的编译器/语言方言都是生来平等的)
- 隔离编译模式代码(
NDEBUG
有人吗?)
Each has the potential to create a huge mess of unmaintanable code, and should be treated accordingly, but not all of them can be dealt with in the same fashion.
每一个都有可能产生大量不可维护的代码,应该相应地对待,但并不是所有的都可以用同样的方式处理。
1. Platform specific code
1.平台特定代码
Each platform comes with its own set of specific includes, structures and functions to deal with things like IO (mainly).
每个平台都有自己的一组特定的包含、结构和功能来处理诸如 IO(主要)之类的事情。
In this situation, the simplest way to deal with this mess is to present a unified front, and have platform specific implementations.
在这种情况下,处理这种混乱局面的最简单方法是呈现统一的前端,并具有特定于平台的实现。
Ideally:
理想情况下:
project/
include/namespace/
generic.h
src/
unix/
generic.cpp
windows/
generic.cpp
This way, the platform stuff is all kept together in one single file (per header) so easy to locate. The generic.h
file describes the interface, the generic.cpp
is selected by the build system. No #ifdef
.
这样,平台的东西都保存在一个文件中(每个标题),很容易找到。该generic.h
文件描述了接口,generic.cpp
由构建系统选择。没有#ifdef
。
If you want inline functions (for performance), then a specific genericImpl.i
providing the inline definitions and platform specific can be included at the end of the generic.h
file with a single #ifdef
.
如果您想要内联函数(为了性能),那么genericImpl.i
可以在generic.h
文件末尾包含一个特定的提供内联定义和平台特定的#ifdef
.
2. Feature specific code
2. 功能特定代码
This gets a bit more complicated, but is usually experienced only by libraries.
这变得有点复杂,但通常只有图书馆才能体验。
For example, Boost.MPL
is much easier to implement with compilers having variadic templates.
例如,Boost.MPL
使用具有可变参数模板的编译器更容易实现。
Or, compilers supporting move constructors allow you to define more efficient versions of some operations.
或者,支持移动构造函数的编译器允许您定义某些操作的更有效版本。
There is no paradise here. If you find yourself in such a situation... you end up with a Boost-like file (aye).
这里没有天堂。如果你发现自己处于这种情况……你最终会得到一个类似 Boost 的文件(是的)。
3. Compilation Mode code
3.编译模式代码
You can generally get away with a couple #ifdef
. The traditional example is assert
:
你通常可以逃脱一对夫妇#ifdef
。传统的例子是assert
:
#ifdef NDEBUG
# define assert(X) (void)(0)
#else // NDEBUG
# define assert(X) do { if (!(X)) { assert_impl(__FILE__, __LINE__, #X); } while(0)
#endif // NDEBUG
Then, the use of the macro itself is not susceptible to the compilation mode, so at least the mess is contained within a single file.
然后,宏本身的使用不受编译模式的影响,因此至少混乱包含在单个文件中。
Beware:there is a trap here, if the macro is not expanded to something that counts for a statement when "ifdefed away" then you risk to change the flow under some circumstances. Also, macro not evaluating their arguments may lead to strange behavior when there are function calls (with side effects) in the mix, but in this case this is desirable as the computation involved may be expensive.
当心:这里有一个陷阱,如果宏没有扩展到在“ifdefed away”时对语句计数的东西,那么在某些情况下你可能会改变流程。此外,当混合中存在函数调用(带有副作用)时,宏不评估它们的参数可能会导致奇怪的行为,但在这种情况下,这是可取的,因为所涉及的计算可能很昂贵。
回答by Some programmer dude
Many programs use such a scheme to make platform specific code. A better way, and also a way to clean up the code, is to put all code specific to one platform in one file, naming the functions the same and having the same arguments. Then you just select which file to build depending on the platform.
许多程序使用这样的方案来制作平台特定的代码。一种更好的方法,也是一种清理代码的方法,是将特定于一个平台的所有代码放在一个文件中,命名相同的函数并具有相同的参数。然后您只需根据平台选择要构建的文件。
It might still be some places left where you can not extract platform specific code into separate functions or files, and you still might need the #ifdef
parts, but hopefully it should be minimized.
可能仍有一些地方无法将特定于平台的代码提取到单独的函数或文件中,并且您仍然可能需要这些#ifdef
部分,但希望它应该被最小化。
回答by Thomas Matthews
I prefer splitting the platform dependent code & features into separate translation units and letting the build process decide which units to use.
我更喜欢将依赖于平台的代码和功能拆分为单独的翻译单元,并让构建过程决定使用哪些单元。
I've lost a week of debugging time due to misspelled identifiers. The compiler does not do checking of defined constants across translation units. For example, one unit may use "WIN386" and another "WIN_386". Platform macros are a maintenance nightmare.
由于标识符拼写错误,我浪费了一周的调试时间。编译器不会跨翻译单元检查定义的常量。例如,一个单元可能使用“WIN386”和另一个“WIN_386”。平台宏是维护的噩梦。
Also, when reading the code, you have to check the build instructions and header files to see which identifers are defined. There is also a difference between an identifier existing and having a value.Some code may test for the existance of an identifier while another tests the value of the same identifer. The latter test is undefined when the identifier is not specified.
此外,在阅读代码时,您必须检查构建说明和头文件以查看定义了哪些标识符。 存在的标识符和具有值的标识符之间也存在差异。一些代码可能会测试标识符是否存在,而另一些代码可能会测试相同标识符的值。当未指定标识符时,后一个测试未定义。
Just believe they are evil and prefer not to use them.
只要相信它们是邪恶的,并且宁愿不使用它们。
回答by William Pursell
Not sure what you mean by "#ifdef is strict no", but perhaps you are referring to a policy on a project you are working on.
不确定“#ifdef 是严格否”是什么意思,但也许您指的是您正在从事的项目的政策。
You might consider not checking for things like Mac or WIN32 or i386, though. In general, you do not actually care if you are on a Mac. Instead, there is some feature of MacOS that you want, and what you care about is the presence (or absence) of that feature. For that reason, it is common to have a script in your build setup that checks for features and #defines things based on the features provided by the system, rather than making assumptions about the presence of features based on the platform. After all, you might assume certain features are absent on MacOS, but someone may have a version of MacOS on which they have ported that feature. The script that checks for such features is commonly called "configure", and it is often generated by autoconf.
不过,您可能会考虑不检查 Mac 或 WIN32 或 i386 之类的东西。一般来说,您实际上并不关心您是否使用 Mac。相反,您需要 MacOS 的某些功能,而您关心的是该功能的存在(或不存在)。出于这个原因,在您的构建设置中有一个脚本是很常见的,它根据系统提供的功能检查功能并#defines,而不是基于平台对功能的存在做出假设。毕竟,您可能会假设 MacOS 上没有某些功能,但有人可能拥有已移植该功能的 MacOS 版本。检查此类功能的脚本通常称为“配置”,它通常由 autoconf 生成。
回答by justin
personally, I prefer to abstract that noise well (where necessary). if it's all over the body of a class' interface - yuck!
就个人而言,我更喜欢很好地抽象噪音(在必要时)。如果它遍布整个类的界面——糟糕!
so, let's say there is a type which is platform defined:
所以,假设有一个平台定义的类型:
I will use a typedef at a high level for the inner bits and create an abstraction - that's often one line per #ifdef
/#else
/#endif
.
我将使用typedef在一个较高水平,为内部位,并创建一个抽象-这是经常一行每#ifdef
/ #else
/ #endif
。
then for the implementation, i will also use a single #ifdef
for that abstraction in most cases (but that does mean that the platform specific definitions appear once per platform). I also separate them into separate platform specific files so I can rebuild a project by throwing all the sources into a project and building without a hiccup. In that case, #ifdef
is also handier than trying to figure out all the dependencies per project per platform, per build type.
然后对于实现,我还将#ifdef
在大多数情况下为该抽象使用单个(但这确实意味着平台特定的定义每个平台出现一次)。我还将它们分成单独的特定于平台的文件,这样我就可以通过将所有源放入一个项目并顺利构建来重建项目。在这种情况下,#ifdef
也比试图找出每个平台、每个构建类型的每个项目的所有依赖项更方便。
So, just use it to focus on the platform specific abstraction you need, and use abstractions so the client code is the same -- just like reducing the scope of a variable ;)
因此,只需使用它来关注您需要的特定于平台的抽象,并使用抽象使客户端代码相同——就像减少变量的范围一样;)
回答by James Kanze
Others have indicated the preferred solution: put the dependent code in
a separate file, which is included. This the files corresponding to
different implementations can either be in separate directories (one of
which is specified by means of a -I
or a /I
directive in the
invocation), or by building up the name of the file dynamically (using
e.g macro concatenation), and using something like:
其他人指出了首选的解决方案:将依赖代码放在一个单独的文件中,该文件包含在内。这对应于不同实现的文件可以位于单独的目录中(其中一个通过调用中的 a-I
或/I
指令指定),或者通过动态构建文件的名称(使用例如宏连接),并使用就像是:
#include XX_dependentInclude(config.hh)
(In this case, XX_dependentInclude
might be defined as something like:
(在这种情况下,XX_dependentInclude
可能被定义为:
#define XX_string2( s ) # s
#define XX_stringize( s ) XX_string2(s)
#define XX_paste2( a, b ) a ## b
#define XX_paste( a, b ) XX_paste2( a, b )
#define XX_dependentInclude(name) XX_stringize(XX_paste(XX_SYST_ID,name))
and SYST_ID
is initialized using -D
or /D
in the compiler
invocation.)
并SYST_ID
使用-D
或/D
在编译器调用中初始化。)
In all of the above, replace XX_
with the prefix you usually use for macros.
在以上所有内容中,替换XX_
为您通常用于宏的前缀。