I remember doing all sorts of coding tricks when writing in assembler and the early days of C when C compilers were little more than glorified assemblers. This is when I would use an XOR A instead of MOV A,0. The former is a single byte instruction while the latter is two bytes. I eventually wrote a massive LET macro that handled equations and generated the most efficient code. LET A = 0 generated the XOR A.
Finally getting my hands on a decent compiler let me ignore this type of programming approach. That’s because, in theory, the compiler should be able to generate the optimum code based on the setting selected by the developer, which can include settings to choose size or speed, etc.
Every programming language and compiler has idiosyncrasies, but for the most part, the best practice for programmers is to make the code clear and to implement algorithms in an understandable fashion. Documentation and comments should not be an afterthought. It still surprises me, though, how often this isn’t the case regardless of the programming language used. And for those who say their code is the documentation, you’re wrong.
This is why I get a bit concerned when reading articles like “10 simple tricks to optimize your C code in small embedded systems.” It reminds me of when I did dumb things like this because in the long run, the cute optimizations warp the algorithm being implemented. This approach can actually make it harder for an optimizing compiler to do its job, potentially inducing bugs that are hard to track down.
Trick #2 in the article reduces the code size by four bytes (5%) by changing a CASE statement into a series of IF statements. The problem is that the example doesn’t use an ELSE clause and essentially trades off speed for size—this only works because the expressions involved don’t modify the conditions involved.
The problem with these kinds of tricks is that if they’re used, they must be very well documented because they always make assumptions that usually aren’t apparent from reading the code. They also tend to be very brittle; minor changes can mess up the algorithm, which often creates bugs.
The smart move in doing embedded C programming actually goes in the other direction, utilizing tools such as Motor Industry Software Reliability Association (MISRA) C. “MISRA C Isn’t Just for Automotive Applications” is an article I wrote a while back to address the myth the MISRA C compliance was only something automotive applications and systems programmers had to be concerned about. The rules involved not only prevent the use of many “features” of the C language, but most of the “tricks” developers often pick up along the way.
One upshot of using static-analysis tools like MISRA C and standard coding rules is that errors are reduced. This includes security-related errors that allow a system to be compromised. Null pointer and buffer overflow errors are less common when following these best practices and using these tools all the time.
It’s much more important to program an embedded system correctly than to squeeze every last byte of the code. This may have been true for very small PICs and 8051 microcontrollers, but these are rare these days. It still pays to use a good compiler and to evaluate your algorithms. However, it’s always a bad idea to use “tricks,” especially without very, very, very good documentation and lots of comments.
If you really want to do it right, you can take a look at Ada contracts. This is a way to make sure that the code is doing what you intend because it’s stated programmatically. There’s nothing comparable to C or C++, but that’s another story.