views:

100

answers:

3

Relying on the preprocessor and predefined compiler macros for achieving portability seems hard to manage. What's a better way to achieve portability for a C project? I want to put environment-specific code in headers that behave the same way. Is there a way to have the build environment choose which headers to include?

I was thinking that I'd put the environment-specific headers into directories for specific environments. The build environment would then just copy the headers from the platform's directory into the root directory, build the project, and then remove the copies.

+3  A: 

This is basically what a configure script does - i.e. work out the specifics of the system and then modify the makefile for that system. Have a look at the documentation for GNU autoconf, it might do what you want, although I'm not sure how portable it would be to windows if that is necessary.

Scott Wales
+6  A: 

That depends entirely on your build environment of course and has nothing to do with C itself.

One thing you can try is to set up your include paths in your makefiles thus:

INCDIRS=-I ./solaris
#INCDIRS=-I ./windows
#INCDIRS=-I ./linux
:
CC=gcc $(INCDIRS) ...

and uncomment the one you're working on. Then put your platform specific headers in those directories:

./solaris/io.h
./windows/io.h
./linux/io.h

You could, at a pinch, even have different platform makefiles such as solaris.mk and windows.mk and not have to edit any files at all.

But I don't see your aversion to the preprocessor, that's one of the things it's good at, and people have been doing it successfully for decades. On top of that, what happens when your code needs to change per-platform. You can abstract the code into header files but that seems far harder to manage than a few #ifdefs to me.

paxdiablo
+2  A: 

pax's answer is good, but I'll add that you can

  1. Mix and Match Handle some system dependencies in the build system (big things, generally) and others with the preprocessor (small things)
  2. Confine the trouble Define a thin glue layer between your code and the system dependent bits, and stick all the preprocessor crap in there. So you always call MyFileOpen() which calls fopen on unix and something else on windows. Now the only part of your code that has any preprocessor cruft related to file opening is the MyFileOps module.
dmckee
This is what I'll be doing, but I'm trying to avoid putting too much boilerplate in the platform-independent code. Having most/all of the platform-specific code in platform-specific headers seems nicer to me. That way, if something gets changed/broken in one platform, I can just change the header to reflect that change. Abstraction is nice, in this case.
Yktula
I would suggest that your goal be zero ifdefs in the mainline source code. Every platform variation should be hidden in header files, or behind an api with multiple implementations. (I also agree with @dmckee's suggestion to put large-scale platform choices into makefiles or build configuration scripts.)
Dale Hagglund