One of the easiest ways to make your code more efficient is to use const wherever feasible. Just like declaring local functions as static, this is one of those changes that makes your code more robust, more maintainable and faster – a true win-win situation. So how does this work? Well you get the most benefit when passing pointers as parameters to functions. Here’s an example of a function whose job it is to compute the sum of an array of integers. The naive implementation would look something like this:
uint32_t sum(uint16_t *ptr, uint16_t n_elements){ uint16_t lpc; uint32_t sum = 0;
for (lpc = 0; lpc < n_elements; lpc++) { sum += *ptr++; } return sum;}
I’ll ignore the issues of post increment and counting up (for now). Instead, consider the declaration of ptr. As it stands, the caller of this function has no idea whether sum() will modify the data or not, and hence must assume that it does. This has obvious implications for the compiler when it comes to optimization. To overcome this, it is necessary to declare ptr as pointing to const. The function prototype for sum() now becomes:
uint32_t sum(uint16_t const *ptr, uint16_t n_elements);
You’ll notice that I prefer to use what I call Saks notation for where I place the const modifier. The more conventional, albeit less sensible way of writing the declaration is:
uint32_t sum(const uint16_t *ptr, uint16_t n_elements);
Regardless of the style, by doing this you are indicating to the compiler that you will not be modifying the data that ptr points to. As a result, the optimizer can make assumptions that will typically lead to tighter code.
Before I give you the final code, I’d like to make a few other observations.
- As well as potentially making your code more efficient, use of const also makes your code more readable and maintainable. That is, someone examining your code will know something extra about the function simply by looking at the prototype. Personally I find this very useful.
- If you examine the C standard library, you’ll find very liberal use of the const modifier. You should take this as a strong hint that it’s a good idea.
- PC-Lint will very helpfully tell you if a pointer can be declared as pointing to const. Yet another reason for using Lint!
So what does my sum() function look like? Well, incorporating my previous hints on post increment and counting down, it looks something like this:
uint32_t sum(uint16_t const *ptr, uint16_t n_elements){ uint32_t sum = 0;
for (; n_elements != 0; --n_elements) { sum += *ptr; ++ptr; } return sum;}
Editorial Note
I’ve been following my own advice and have been on a short vacation. As a result I’ve been tardy in responding to some of your comments. I’ll try and rectify this over the next few days.
While const is used commonly in the standard library, there are also some notable places where it should be used but isn’t. For example, strstr and strchr take parameters of type const char *, but return a char * into the same string.They in essence remove the const qualifier without any warning that operations of the resultant pointer might cause problems with assumption the optimizer will make.
Hi Nigel,Why do you use the following code?sum += *ptr;++ptr;Instead of:sum += *ptr++;The last of the two saves me 4 bytes of code with the compiler I use.Regards,Dennis
In general, a post increment is sub-optimal as I explained here. When you performed your test, did you have the optimizer turned fully on? If so I’d be interested to know what compiler / target you are using.
In my test project I did not use any optimzer. After turning it on the two codes did not give any difference in size. After reading your other post I decided to ban post increments.ThanksDennisP.S. Using Keil uVision with ARMCC v3.1.0.942
Dear Nigel, i religiously follow your blog (and advice too). I was wondering if you can write an article on your view of callbacks, plain simple C callback functions.I’ve come across many people who use callback functions when it is not at all required. People often forget the fact the callbacks execute in the context of the calling task. In some cases that is how it should be done (like an application specific memory allocation routine) while in other cases its not the right thing to do.I believe people lack the art of defining a finite scope for the task, what data it operates on and how it should communicate with other tasks. That said, i am no authority over C and designing complex systems.I hope you got what i am aiming at. I am finding it hard to explain. Please, if you can spend your precious time sharing your experiences with callbacks, that would be godsend
Thanks!
Hi Anonymous:I think I know what you are getting at. Since I’m a big proponent of pointers to functions I think it’s only fair that I tackle their dark side, so to speak. Look for a posting on this in the next week or two.
Re Anonymous…Callbacks, and threads, are 2 areas that are tackling by the unwitting thinking its all terribly easy. Then they blame the compiler for the race conditions, stack overflows and deadlocks that result.Such people terrify me.I spend 10 years writing concurrent mult-thread-multi-task programs and I'm well aware of the difficulties and pitfalls. Those who leap in get burned – every time. A place to go with great care.
- ashleigh,it would be great then if we could have a return of your experience in such embedded systems, multiprocess multithread systems are really a nightmare for every programmer.Some good experienced directions would be greatly appreciated.Fahmi MEGDICHE