7 ms·
#ifdef considered harmful (1992) [pdf]
- beefhash 8y agoIt bears noting that at some point, you will likely need to #ifdef. There's no way around it for non-trivial code. Simple example: Getting the size of a file if said file is greater than the maximum size of long, meaning you can't fseek to the end and use ftell(); you'll need platform-specific APIs like stat(2) or GetFileSizeEx(). You can also do the platform checks in the build system instead and have multiple per-platform C files implement APIs for portability shims -- but given that the platform check is likely unportable (at least in make as specified by POSIX), you're just making different portability assumptions.
- dvfjsdhgfv 8y ago> You can also do the platform checks in the build system instead and have multiple per-platform C files implement APIs for portability shims -- but given that the platform check is likely unportable (at least in make as specified by POSIX), you're just making different portability assumptions. And yet, it seems that's what the authors of the article did: The best method of managing system-specific variants is to follow those same basic principles: define a portable interface to suitably-chosen primitives, and then implement different variants of the primitives for different systems. The well-defined interface is the important part: the bulk of the software, including most of the complexity, can be written as a single version using that interface, and can be read and understood in portable terms.
- avar 8y agoI may be wrong, but I was under the impression that this was portable on all POSIX systems: uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') ifeq ($(uname_S),Linux) HAVE_LINUX_SPECIFIC_STUFF = Yes endif This is how git does its conditional compilation purely powered by Makefile logic: https://github.com/git/git/blob/master/config.mak.uname https://github.com/git/git/blob/master/config.mak.uname
- LukeShu 8y agoThat requires GNU Make; neither $(shell) nor ifeq are in POSIX Make.
- jwilk 8y ago":=" assignments are not in POSIX either.
- jstanley 8y agoMakefile logic is basically just an ifdef one level up.
- chris_wot 8y agoSo what do you do then?
- convolvatron 8y agoany place you have to parameterize signatures or behaviors or types, calling it something meaningful and define it in a platform/compiler/os specific header. sure, in the end its the the same thing, but i find it to be easier to manage over time. and when you need to port to a new platform/compiler/os, you have a list of the important dependencies at hand...maybe you have to define a new one
- jstanley 8y agoI don't consider ifdefs harmful. But if you do, you must surely consider logic in Makefiles equally harmful.
- chris_wot 8y agoWhy so?
- monocasa 8y agoBut your per platform build logic pretty much has to exist anyway on any non-trivial project, as differences in compilers (even different versions of the same compiler) crop up. Better to just stick that distinction all in one place, IMO.
- ape4 8y agoYes, nobody likes using #ifdef but if you need it, its great that its there
- pjmlp 8y agoHence, why the abstraction translation units should be named according to their abstraction level. Meaning module_posix would be for all POSIX platforms, whereas module_aix would be Aix only.
- Avamander 8y agoLet's not forget microcontrollers, the moment one wants to write something more universal (different MCU support) using ifdefs is basically unavoidable.
- pjmlp 8y agoNot when using one translation unit per MCU and letting the Makefile pick the right one. Back on my C days, I was able to write portable code between Windows NT, Windows 2000, Red-Hat Linux, Debian Linux, Aix, HP-UX, Solaris where the #ifdef were only used as include guards, regarding my own code.
- monocasa 8y agoSeconded. And it's only gotten better, as uc code is about the best case for LTO being both small and all in one final image. Now there's not even the specious argument about "artificial" compilation unit boundaries hurting performance terribly.
- namibj 8y agoAlso, you'd be surprised how heavy software can get without outgrowing single-unit LTO, as long as you don't try to pull in all of Qt/GTK or similar bloat in. (I don't see those as a bad choice, but they are too heavywight for this style of LTO to work well.)
- Avamander 8y ago> Not when using one translation unit per MCU and letting the Makefile pick the right one. Could you please link any examples of that I could examine myself? Also, makefiles aren't really an option with platforms like Arduino, the IDE does not really support them.
- namibj 8y agoErr, you can use the Arduino ecosystem with gnu make.... look for it, there is documentation, which does have ease of use to wish for and requires installation that drives me towards believing in chroot.
- yzmtf2008 8y agoSimilarly, if you’re writing an native extension targeting multiple versions of ruby, python, etc., ifdefs are unavoidable.
- JdeBP 8y agoIf someone wants to fix Wikipedia's article on Henry Spencer, a full citation just waiting for {{cite conference}} is at https://news.ycombinator.com/item?id=17610233 https://news.ycombinator.com/item?id=17610233 .
- arkj 8y agoEvery construct can be harmful if one doesn’t understand its purpose.
- fanf2 8y agoI’ve been maintaining unifdef since about 2001. One of the rules I have set myself is to never use #if in unifdef. It’s an easy program to port, so this isn’t very hard :-) but Microsoft’s appalling C libraries make it more painful than it should be. The main technique is to set the include path depending on the behaviour of the target platform, and have per-platform variant header files. For unifdef, this basically means standard vs windows. The other thing I don’t have to deal with is build-time configuration options or feature switches. It’s easy to get into a combinatorial explosion with #if but kinda hard if you force yourself to reify each option as a pair of include files!
- snarfy 8y agoIsn't this just moving the goal posts? Now all of your #ifs and the complexity they bring are in your build system.
- tedunangst 8y agoPerhaps there are fewer conditionals in the build than there would be in the code.
- monocasa 8y agoBut your build system will probably need that platform specific piece anyway, so the question is more "should we have one or multiple switches on platform type".
- JdeBP 8y agoIt already was in the build system. The macros that control conditional compilation when one is doing it with the preprocessor do not appear out of thin air. A build system has to set them appropriately in the first place. All of the mechanism for auto-detecting (or manually specifying) the capabilities of the target have to be there. It is simply a case of whether they set preprocessor macros via command-line options or tell the build system to use alternative source files. The latter has the advantage of only creating build dependencies against the modules that actually use the library functionality in question, rather than against the compiler command line and potentially forcing a re-build of everything (when an honest dependencies mechanism is being used).
- pandaman 8y agoThe article does not seem to mention any uses of conditional compilation outside of portability. But consider the most common case: debug and release versions of the same program. You want the extra checks and diagnostics in the debug version and fastest code in the release. Using assert() will only get you so far because you might need to compute values to assert on and if those require function calls then the calls will still be there when asserts are eliminated in the release. And if you want additional room in your data structures to store debug info then assert() is of no use.
- eesmith 8y agoWhen you say "the most common case", I assume you mean "the most common case now", and not in 1992 when this paper was written. The abstract does start "We believe that a C programmer's impulse to use #ifdef in an attempt at portability is usually a mistake" so it's fair to assume that they were not trying to cover other uses of #ifdef. That said, your points weren't completely ignored. Page 193 mentions #ifdef use in debug builds in the text starting "An optional feature such as debugging assistance ...". The part about "additional room in your data structures" follows under the guideline "Such declarations, in turn, preferably should be confined to header (.h) files, to minimize the temptation to introduce #ifdef into main-line code".
- Annatar 8y ago“Debug” and “Release” only pertains to Windows. You are always supposed to build with -g on UNIX and UNIX like operating systems because besides a few miliseconds of startup time, including the source code into the binary doesn’t affect anything but file size. The runtime linker skips those sections and links only code and data into memory, but the source code is there if you load it into the debugger. Kernel engineers taught me this.
- pandaman 8y agoThere are much more differences between debug and release than having debug info included in the executable so your reasoning makes little sense. Many people, for example, build games for PS4, which runs a FreeBSD Unix-like OS, unawares that their debug and release builds only pertain to Windows...