Most of the C and C++ preprocessor statements I have seen smell. --
JasperPaulsenHere are some of the accepted ways to use
CeePreprocessorStatements:
- IncludeGuards. IncludeGuards smell, because they exist solely to protect duplicate code (in header files) from being triplicated. This is a LanguageSmell, not a CodeSmell. That is, if you use C++, you are better off using include guards than not using them, because the language design forces you to.
- RedundantIncludeGuards. Enough said.
- In the implementation of assert macros, to prevent a performance hit at run-time. This is an excellent practice, except that it means that any otherwise useful code performed by the assert statement becomes an undesirable side-effect.
- Macros. In-line functions work just as well. Functions don't bomb the program if they are not wrapped in enough parentheses. Functions can be stepped into and debugged; most debuggers cannot step into macros. Most importantly, function calls always evaluate their parameters only once. (Try using 'x++' in a 'min(,)' macro. Surprise!)
- To create enumerated lists (e.g. of Windows dialog controls), without creating an enum. (Yuck!)
- To comment out blocks of code from the compiler. (We can't use actual comments because in C++ comments don't nest. We can't use "if(false)" because the commented-out code may not compile, and because the removed block may be bigger than a single function.)
- Stringify code. For example, stringifying a failed assert expression.
- Conditional compilation. Used in a variety of ways. For example:
- Enable Multiple Character Types -- compile differently based on a static character type of Single-Byte, Multi-Byte, or Double-Byte.
- Enable Multiple Platform Builds -- compile differently based on platform. For example, typedef a Unicode character type as uint16 #ifdef _WIN32 but as uint32 #ifdef _UNIX.
- Enable Multiple Build Grades -- change the verbosity or rigor of debug code based on the BUILD_GRADE. For example, a build may have the following grades: DEBUG, INTEG1, INTEG2, and RELEASE.
- Defining booleans in C. (Why not use "enum bool { false, true };" instead?) [Because true in C is any non-zero value it makes more sense to #define FALSE (0!=0) / #define TRUE !FALSE]
- Getting the file name and line into a debug statement (__FILE__ and __LINE__). This is especially useful when the target system's debugger is incompetent. However, using this creates some interesting UnitTesting problems (using (__LINE__ + 2) for the expected error statement, for instance).
Here are some less accepted, but still very common, uses of
CeePreprocessorStatements:
- To make it easier to use the debug version of the program than to use the release version of the program. For example, to create a backdoor around the password protection system. If the developers think that the system is too much of a hassle to use regularly, shouldn't they make it easier to use for everybody? Plus, if the developers don't use a feature, it is much less likely to be properly tested. -- generally this is done in order to get to the piece of functionality that needs testing without wading through all of the startup steps on the way there. However, if you're unit testing properly, you shouldn't need to do this.
- Many kinds of configuration management where a variation in the program can and should be fixed at compile time.
Discussion:I implemented rudimentary reflection in C++ using macros. The preprocessor is my friend. I especially enjoy the # and ## operators. On the other hand, having read the Apache source code and the MFC headers, the #ifdefs are brutal. Trade offs, trade offs. Might as well outlaw gotos while you're at it. --
SunirShahActually, I'm with Stroustrup on this. The C preprocessor is nasty. There is very little to enjoy about the # and ## operators. In general, the C preprocessor does little but expose the deficiencies in the
CeePlusPlus language. --
RobertDiFalcoWell, it's certainly not the
best thing ever. Scheme and Lisp have better macro support. Imports are better than includes. But it's workable and powerful and certainly usable to get things done during the day. The same apologetics are used for Java, I know, but at least most of C++ was thought out. (And Java is in desperate need of a macro system, too.) Besides, the preprocessor is just an inheritance from C. Two competing forces: elegance vs. backwards compatibility. --
SunirShahThat would just be horrible if Java added a preprocessor! Ugh! How is Java in desperate need of one? For stringifying assert expressions? Big deal! The problem with the C preprocessor is that most people over-use it. They end up using it to create mini-languages or object-models that are difficult to maintain and usually over-complex. The C preprocessor should be something one uses when there is simply no other choice. --
RobertDiFalcoOften a problem with Java in constrained environments such as embedded devices is that the some of the class library or even some of the language (e.g. reflection) is not available on the platform. Sometimes, the best choice to minimize footprint is to conditionally compile. --
SunirShahjava.lang.Reflection is actually part of the libraries, not the language.I'm not sure what you are saying, but you can conditionally exclude byte-code in Java using static finals. --
RobertDiFalcoTrue. Though the compiler would still have to evaluate the code block even if it was not emitted. One problem might be making use of java.lang.* classes that don't exist on a impoverished platform. I have had to write code that didn't use all of java.lang.* because it wouldn't compile in certain environments. Unfortunately, since I don't have a cross-compiler handy, I cannot verify or demonstrate my experience. --
SunirShah
IncludeGuards smell, because they exist solely to protect duplicate code (in header files) from being triplicated. - Can you explain what is wrong with wanting to do this, or explain a better way of doing it? Move to the
IncludeGuard page if you like. --
DaveHarrisThe smell shows a design flaw in C and C++: Linking code together efficiently requires duplicate code (in header files). Given this design flaw,
IncludeGuards are the best work-around I know of. --
JasperPaulsenExactly, and Stroustrup often talks about this flaw. He's often lamented his disappointment in the need for the
CeePlusPlus precompiler. --
RobertDiFalcoTry doing your include guards like this: #ifndef ___MY_H #define ___MY_H #else #error Multiple includes of MY.H are not allowed #endif
Talk about enforcing discipline! -- EdwardKiserThis
is not a good idea. See
LargeScaleCppSoftwareDesign for a related discussion. --
DirckBlaskeyI see include guards as a way of reducing coupling. If a .h file must see the definition in another, then there are 2 possibilities: either you require the user of the .h to include the dependencies, or you need to have the .h file include another. There are good arguments for each but, in general, I favour not forcing the user of a class to know its dependencies. This limits the scope of changes because it reducing the coupling. --
DaveWhippAll C and C++ standards committees have to do is agree on a proper module system for these languages. It is not impossible to do. Only their never-ending white-knuckled grip on holding on to the past prevents them from overcoming the "C as a better assembly language" mentality. It is not a crime to borrow ideas from NiklausWirth! Introducing a new module Foo {...} keyword isn't going to destroy the planet. Moreover, introducing a new import Bar.Baz as Bletch; construct will not affect the galactic alignment. We have been waiting for these features for decades!!!! Please, please, please, please, please, please, please, please, please move on! This is the 21st century for Christ's sake!
If someone attaches a smell to a general language feature (e.g. the C preprocessor) then we are in an extreme form of
LanguagePissingMatch ("this whole language smells"). This should not be done. Languages have weaknesses, it's our job to avoid them. A smell is something that can be avoided because there is a better way to do it. There is no way in C to avoid the preprocessor, so the C preprocessor can't smell. Only a certain use of the preprocessor can smell, if there is a better alternative. --
HelmutLeitnerI see the C preprocessor feature as similar to a "goto" statement:it is a low-level language feature that can be used to fake high-level stuff like a module system. In general, one should use the high-level feature if available. If not available, one should stick to a highly stylized idiom that simulates the high-level feature. And complain loudly to the C standardisation committee. --
StephanHouben
The preprocessor was a very good thing for the "C" language.It is largely useless in the C++ language, and should generally be avoided.
- Macros used as Inline Functions:
- Use inline functions. Inline functions evaluate their parameters only once. Inline functions are easier to debug. Inline functions have a more rational syntax -- the definition does not have to "all be on one line." Inline functions are type safe and can be overloaded. Inline functions conform to name spaces and scoping rules. Use inline functions.
- Macros used as Constants:
- Use enum or 'static const'. These are type safe.
- Function Pointers:
- Use 'class' and 'virtual'. (OK, it's not a preprocessor feature; I just threw it in here.)
The only remaining acceptable uses of the preprocessor in C++ are...
- ConditionalCompilation? -- but in most cases polymorphism is a better idea.
- IncludeGuards: Yes they smell, but they're an accepted idiom and are isolated to the include files. One should strive to minimize them by using forward declarations and interfaces, but this won't get rid of all of 'em.
Well, it's certainly not the best thing ever. Scheme and Lisp have better macro support.You should not compare the C preprocessor macros with
LispMacros. The formerare simple textual substitutions [
Note however that the C preprocessor operates on a sequence of tokens, not on a sequence of characters.] the latter are full fledged programswhich can arbitrarily re-write the expressions they are working on.[I'm actually not sure if
SchemeMacros allow this, but
LispMacros do.]
Okay.. So the preprocessor is evil. I'd like to change these MIN & MAX macros to C++ inline functions. I'm not especially worried about MIN(a++,b++) etc, because everyone knows about the double increment of the lesser variable, but there are MIN(vec1.len(),vec2.len()) or other function calls which could be bad (perhaps optimized away though). So I'm thinking 'What better way is there to do it than to use a template function?'I'm going from
#define MAX(a,b) (((a)>(b))?(a):(b)) into
template <class T> inline T MIN(T a, T b) { return a<b?a:b; } and rebuild the product. But there's problems ahoy! MIN(type1, type2) is not defined. For example double and float, or double and char, double and int, etc etc... Overloading is a hassle because there's so many types.How can you overcome this problem with this incredibly simple macro?
Well, you should use std::min or std::max from <algorithm>, rather than rolling your own. Of course, you can't call these methods with arguments of different types, but you can explicitly select a specialization. Example stolen from Josuttis: int I ; long L ; // std::max(I,L) // This is an ERROR std::max<long>(I,L) // This is allowed.
AndreiAlexandrescu wrote an article on max as well. http://www.cuj.com/experts/1904/alexandr.htm?topic=experts
Moved from TrivialDoWhileLoop:If one writes a macro, the discussion above shows how to let other people safely use the macro in their code.Macros should not be used
unnecessarily.Some disadvantages of macros:
- The compiler can be trusted to read and expand every nested include file, but humans can't.
- It is common to define C macros in headers. Nested includes often cause macros to interfere with each other in ways that are difficult to untangle.
- If you choose to use header files, there are also standard techniques for making sure they don't tangle up with each other.
- Using header files is quite a different topic than using macros. Many languages use something along the lines of header files, albeit with something like the #import unique inclusion approach.
- People don't hate header files as much as they deplore macros.
Macros can do some things functions can't do. Some people would rather not do those things. Among them:
- You need to pass an argument which is not a valid expression in the calling scope, such as passing the name (or partial name) of a type or variable that you intend on defining in the macro's body. You cannot pass an unevaluated symbol into a function - use of a symbol as a function's actual argument will either cause the symbol to be evaluated, or a compiler error (if the symbol is not defined or has a type mismatch).
- You need to pass as an argument something which isn't an expression at all!
- Interpreters are often written by GenericProgrammingUsingCeeMacros. (Some people prefer to use EBNF and ANTLR instead of C, when writing interpreters.)
- CallByName semantics are needed. Most folks think CallByName is evil, but occasionally it is handy.
- Sometimes you need inlining with macro semantics ("unhygienic macros", in Lisp speak), that access globals or what not (and don't lecture about globals; they too are rare, but not nonexistent).
- Here's one I've done moderately often. In debugging via an assert() that calls abort() on failure, in one environment abort() calls raise() which calls kill(), so in the debugger I have to keep going up multiple frames, and the conceptual bottom-most frame isn't the actual bottom-most. So I do #define abort() kill(getpid(), SIGKILL) to avoid the clutter. Using a function would not do what I want.
- You need access to the enclosing scope - a macro is the closest thing C/C++ has to a LexicalClosure. From a macro, you can return from the enclosing function (try doing that in an inline function - all you will do is exit the inline function. You can also access the local variables in the procedure enclosing the macro call; inlines can't do that either.
- Implementing something as a macro allows for it to be used as an lval, not just an rval: #define INFO(x) ((x)->information_record.user_information) allows usages like INFO(a) = INFO(b); which, in some circumstances, can be considerably nicer than the alternatives.''
Speed- You want to force the compiler to expand something inline (declaring it as "inline", or "__inline" on older compilers, is merely a suggestion and not a requirement). This case should be rare, as most modern compilers will respect inline directives (with optimization enabled); and there are better ways than #define to declare manifest constants. Rare, but it still happens...
- The stdio getchar and putchar as implemented by Bell Labs/ATT (and by many later vendors) were macros for two separate reasons: speed, and to access certain stdio singleton global variables. The GNU /usr/include stuff also implements them as macros but I never examined them to see why. The speed difference of getchar() vs fgetc() was quite noticeable back in the old days. Not sure about these days.
- Encapsulation: ctype isdigit etc as a macro hides whether the implementation is done as a function, lookup table, inline expression, etc., which is a good thing, and also since the point is extreme speed, typically it is implemented as a table lookup, but wrapping that in a function rather than a macro would defeat the speed and encourage people to reinvent their own isdigit/isalpha etc.''
Exception SafetyConsider
X* f() { return new X; } Y* g() { return new Y; } try { do_something(f(), g()); }
This code can be exception safe. However, if you replace f() and g() with equivalant macros, then it cannot. This is because the C++ standard explicitly does not require naked "new" operations to completely create one object before starting on the next. So, in the macro-based implementation, the compiler is free to allocate memory for both X and Y before calling either of the constructors. If the first of the constructors (we don't know which will be first) throws an exception, then the memory for the other might not be reclaimed. When the "new" operations are encapsulated in functions, this optimization is not permitted. (Which means that even inline functions might be slower than macros).
Debugging- As previously mentioned, for pass by reference... which is not always evil. For instance it is sometimes handy during debugging to redefine a widely used function from pass-by-value to pass-by-reference via a macro, just temporarily, without needing to change 50 source files to go back and forth.''
- To turn debug prints on and off e.g. #if ...#define DPRINT() ... etc.''
I fully understand people's distaste for the C preprocessor, but nonetheless I can't fathom living without it, either. The above stuff all applies even when using C++, for that matter; they didn't come up with enough features to completely displace it.
Language idioms are things that all experienced programmers in that language learn. [In my experience, this is not a common C language idiom.]
[My primary audience (after the compiler, of course) contains many junior programmers. Some are less bright than others, but they all deserve code that is as easy to understand as I can make it.]
IMHO the C preprocessor is the WORST macro processor in wide use.
Probably. Just for the record, for years it wasn't considered part of the C language per se, and was advocated, designed, and implemented by different people than the rest of C: Alan Snyder, Mike Lesk, and John Reiser. See Ritchie's history of C http://cm.bell-labs.com/cm/cs/who/dmr/chist.htmlIncidentally, skipping past the design of the C preprocessor for a moment, Reiser's implementation of it is an amazing case study in how to make software run really, really, really fast. In one environment I used in the 1980s, it was faster than "cat file.c > /tmp/foo"! ("cat" uses getchar() and the stdio lib was inefficient)
The GNU coding standards recommend if {...} else {...} instead of #ifdef ... #else ... #endif
http://www.gnu.org/prep/standards/standards.html#Conditional-CompilationWhich is perhaps fine for FSF posix-only "free" code, but doesn't work well when one of the branches uses undefined identifiers, which occurrs quite often when you're doing portability.
I use not only the C preprocessor, I program in the preprepreprocessor!Here is one example, which is used to automatically unroll loops. Preprocessor commands can also be included in the unrolled loops.
@m Repeat@T ``yg_Repeat @) @m _Repeat@T J@ A-g_Repeat @) @m Repeat@W q/@tRepeat Bq``: zyqnB" times:\ @> qA"@# @)
Here is another example. \addkind is a TeX macro, defined earlier in the program, which sorts and prints a summary of the entries.
@mkind@T ``k"@<Data of kinds@>= "#define kind (kind_data[_kindnum]) "#undef _kindnum :#z:dz:ez:fz:iz:nz:ez: z:_z:kz:iz:nz:dz:nz:uz:mz: z :0z:xzu;qA"strcpy(kind.name+1, :"zqu;qBz");kind.character= :0z:xzqu;qA";kind.color= :0z:xzqu;qA";kind.menu= qu;qA";kind.selection_key= qu;q:'zBz";kind.cycle= qu;qB"; qgkind_ @) @mkind_@T xJ@ z"_kind_flag; gkind_ @) @mkind@W "\addkind% B";;% @) @mname@- ``YJ"@ q/@d : zBqA @)
CeeRefactorStringsToFunctionsCategoryCee CategoryCpp CeeIdioms