7 ms·
I follow the advise of Scott Meyers to prefer the compiler over the pre-processor. Pre-processor code can inject bugs that which can't be easily traced with de
by emcrazyone 11y ago
I follow the advise of Scott Meyers to prefer the compiler over the pre-processor. Pre-processor code can inject bugs that which can't be easily traced with debugging tools.
The pre-processor is pure evil and should be avoided when ever possible.
- PeCaN 11y agoIf you want to debug preprocessor-generated code, -gdwarf-4 -g3 usually does the trick with GCC and Clang. If it doesn't, you can just gcc -E it and run it through clang-format or something, and compile and debug the expanded version. It's really not a big deal. You can have a Makefile rule to do this automatically, for example https://github.com/alpha123/yu/blob/master/Makefile#L70-L103 https://github.com/alpha123/yu/blob/master/Makefile#L70-L103 Saying the C preprocessor can "inject bugs" and make code that "can't be easily traced" is deceptive and wrong. It is, for all intents and purposes, a solved problem. That Makefile rule could be 3x shorter if I didn't go for some extra niceties like not expanding system headers. (As usual, 20% of the result takes 80% of the work). The preprocessor doesn't "inject bugs" anyway, unless you write very careless macros. Modern C, with things like __typeof, _Generic, and GCC/Clang's statement expressions means your macros can be totally safe against overwriting variables, double-evaluation, type errors, and more. You can, of course, still do silly things, but C isn't about protecting the programmer anyway, and that's totally fine if you're prepared for it. Perhaps in C++ the preprocessor is ‘pure evil’, but in C it's a very useful part of the language.
- emcrazyone 11y agoI work in the embedded space and use a compiler from Green Hills. The preprocessor in either C or C++ is evil for the simple matter you can't debug it when the thing you're debugging is in production, symbols are stripped, and the disassemble resembles nothing you can marry up to actual C or C++ code. In simple cases, maybe, but in not so simple cases it's just evil. A modern compiler will optimized out the global consts anyways too...
- kllrnohj 11y agoIf you have a modern compiler then it supports outputting the pre-processor result. If you have a modern toolchain stack unwinding is already friendly to macro expansion. A production build does not mean you don't have symbols, it just means you don't have symbols in the executable. Stash your build symbols somewhere so you can symbolize the stacks offline. Macros don't change this. They are consistent output for the same input. Macros are not evil. Abusable, yes, but they also solve various sets of problems just better than any other solution.
- emcrazyone 11y agoOutputting the pre-processor result is not that helpful, you're still stuck with deciphering things. macro expansion is useless, again, for same reason you can't marry it to actual code. If you can do this now, would be at most curious (not interested) as it's evil to begin with. A production build in the embedded world, typically means you don't have symbols. Often they are stripped to save space and unless you have a USB stick or something you carry with you with map files, you're still toast. And there are a whole host of other reasons why they are just pure evil: 1. Global scope only. 2. Unexpected results rather than error messages - ISO25259 won't even allow you to do this if that tells you anything. 3. Can't use sizeof 4. no type checking. Every compiler I've used, including GCC, won't warn about if an int is compared to unsigned. 5. can't take the address 6. worst, substituted value need not even be legal in the context the #define is created because it is evaluated at each point it is referenced allowing to another evil problem of being able to reference objects that are not declared yet. I could go no... and I haven't even touched on strings yet. with so many problems, I have a hard time justifying or championing their use.
- kllrnohj 11y agoThe vast, vast majority of your "evil" list is either just wrong or unrelated. I don't think you really know what macros are or how/when to use them.
- kstenerud 11y ago> 1. Global scope only. So are functions, by that rationale. If you want to control scope of variables, pass in macro parameters. > 2. Unexpected results rather than error messages When have min() or max() produced unexpected results rather than error messages? They're macros, you know. > Every compiler I've used, including GCC, won't warn about if an int is compared to unsigned. -Wconversion > 5. can't take the address You can't take the address of a struct definition or a typedef, either. Macros are not replacements for functions; they're code generators. > 6. worst, substituted value need not even be legal in the context the #define is created because it is evaluated at each point it is referenced allowing to another evil problem of being able to reference objects that are not declared yet. I don't even... You do know that, following the preprocessing phase, the compiler actually checks the generated code for legality, right? And since when is forward referencing evil?
- Mikhail_Edoshin 11y agoIt's not pure evil. It handles `#include` for one. And I can't see how you can write cross-platform code without platform-specific macros or conditionally processed sections.