4

I am working on a USB stack in C. This stack will be open source and used in various different projects.

Various configuration options are available, which enable or disable large chunks of code and constant data structures. As this is an embedded project it is important to minimize the footprint of the stack, and one of the benefits of disabling unused features is a reduction in flash and RAM use.

There are two ways to remove unused code from the build process output. Using #ifdef preprocessor directives causes the compiler to not even consider the unusued code. The main disadvantages are polluting the code with lots of #ifdefs and the potential to break disabled code and not get a compiler warning/error.

The other option is to include the code but allow the linker to eventually remove it. GCC is very good at this, and the resulting output is the same size as if it was removed with #ifdef. The down side to this is that it requires at least a minimum level of optimization to be enabled (-O1, functions in sections and unused section removal) and this behaviour isn't guaranteed on other compilers, or even other versions of GCC.

What is the best practice here and why? What are the advantages and disadvantages I have not considered?

  • Have a look at this advice. Having compiler flags for optimisation level is normal practice – Caleth Mar 16 '18 at 11:09
  • I edited the question to clarify, but this stack will be open source and used in other projects. Compiler flags force any project using the stack to adopt them, although in this case I don't think anyone would not want them. In fact I think -O0 is not really valid for the AVR platform anyway. – user Mar 16 '18 at 11:26
  • If you would decide for not using #ifdef's, how precisely will your configuration options then be controlled? – Doc Brown Mar 16 '18 at 22:45
  • I would use #ifdef for configuration, but for example not bother to surround every bit of code that won't be needed with them. The compiler will then notice that code is never called and remove it. – user Mar 19 '18 at 13:28
4

Go for preprocessor directives. They shouldn't pollute too much the code (if they do, you can probably refactor those sections and extract them to a separate, possibly inlined, function, increasing its maintainability).

In my opinion, the most important point is that you can't know whether that section of code will actually be used or not. Today's modern IDEs can highlight or dim those sections depending on the configuration you're building to, when inside an #if...#endif block. This makes that code more maintainable and easy to identify. Relying solely on the compiler/optimizer will make that clear just to itself, with no practical advantage in terms of efficiency, performance, or program size.

  • 4
    This answer completely ignores this severe disadavantage mentioned in the question: "the potential to break disabled code and not get a compiler warning/error.". – Doc Brown Mar 16 '18 at 22:14
  • 1
    Right, although that's a testing problem, not a coding problem. The opposite (leaving the code without stripping it out during the preprocessing phase) also has a side effect: The programmer may assume it's not used but is actually used with critical consequences. In the end, the right way to test the program is with and without that code being used. Just like testing any conventional conditional branches. If any code won't be run in either case, it should be removed instead. – Jesus Alonso Abad Mar 16 '18 at 23:03
  • This illustrates a common problem with testing embedded code. Traditional methods like unit testing don't really work - the only way is to build all the different configurations, flash them to an MCU and run some USB test code on the host. Tools to do that don't exist, you have to build something from scratch yourself. – user Mar 19 '18 at 13:29
2

Optimizing by removing unused code is one of the oldest optimizations out there. Remember computers used to have much fewer resources. Many of today's embedded platforms have more resources than desktops 30 years ago. If your embedded toolchain doesn't remove unused code, it's frankly a piece of junk.

#ifdefs are hard to read and hard to maintain. You are never going to keep up as well as pretty much any compiler written in the last 40 years that isn't an undergrad class project.

  • So you favor compiler flags, even though they potentially tie you to a specific compiler? – Robert Harvey Mar 16 '18 at 17:08
  • 1
    My point is it won't tie you to a specific compiler, because any compiler worth using will have an option for this. Yes, you potentially have to change flags when you change compilers, but that's a reasonable expectation when changing compilers, especially in the embedded space where you'll most likely have to change it anyway for flags like -mmcu, which tend to differ a lot more between compilers than more standard flags like -O1 or -Os. – Karl Bielefeldt Mar 16 '18 at 17:24
  • I get it now. Might want to add your last comment to your answer. – Robert Harvey Mar 16 '18 at 17:27
  • You also might want to explain a bit better the process by which compilers remove the unused bits. I assume that the C compiler does this by removing functions that never get called by the client. – Robert Harvey Mar 16 '18 at 17:30
  • If you are going to trust the compiler to do this (and indeed, often you should) it may make sense to occasionally make sure it actually is. For example, I found this page while trying to figure out why a compiler's linker is leaving behind strings used only in code that itself is never called, and has been dropped from the executable. – Chris Stratton Sep 30 '18 at 1:18
2

I believe the correct answer to "#ifdef or linker optimization" is YES.

I would use #ifdef around large, major parts that might not be utilized (for example, USB 3.0 support-specific code) to permit the developer to perform a "rough trimming" of the final object themselves, then let the linker perform the final optimization.

This has the benefit of giving some control to the developer for code-space optimization at the large-scale while not requiring them to use high-level optimization in the linker stage if they don't want to (remember, high-level optimization can refactor the code so much that when single-stepping in a debugger what the assembly/machine code is doing has very little resemblance on the C code as written).

-1

"This stack will be open source"

conflicts with

"this behaviour isn't guaranteed on other compilers, or even other versions of GCC."

If other people build the source you can't rely on them using your approved compiler.

Say I use your project but for whatever reason i have to use a compiler which doesn't optimise the code so well.

I will be forced to fork your project, add the #ifdef statements and publish it as 'USBStackProject_ForOldCompilers' or something.

If you were distributing the binaries I don't think it would matter how you produced them. But you should make the code you distribute work with as many compilers as is reasonable.

  • 1
    True, but I can certainly say "I won't support this project if it's being used on anything but gcc". – Philip Kendall Mar 16 '18 at 13:01
  • 1
    You can, but then my project is exactly the same but supports more compilers. Presumably you want people to use your project rather than my fork of it? – Ewan Mar 16 '18 at 13:07
  • 1
    I'm not supporting it at all, it's my gift to the world. – user Mar 17 '18 at 12:23
  • It is by no means unheard of for open source projects to be tied to a particular compiler, or even a particular version or at least to exclude a particular version. Of course others can fix whatever issues are behind that, but such advisories happen. Tying to a particular compiler would be due to features; tying to (or avoiding) a particular version would be due to compiler bugs. – Chris Stratton Sep 30 '18 at 1:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.