embedded software boot camp

Efficient C Tips #8 – Use const

Monday, March 30th, 2009 by Nigel Jones

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;
}

Next Tip
Previous Tip
Home

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.

Tags:

19 Responses to “Efficient C Tips #8 – Use const”

  1. Uhmmmm says:

    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.

  2. Anonymous says:

    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

  3. Nigel Jones says:

    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.

  4. Anonymous says:

    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

  5. Anonymous says:

    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!

  6. Nigel Jones says:

    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.

  7. ashleigh says:

    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.

  8. Engineer says:

    – 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

  9. Peter says:

    Hello Nigel,
    Why don’t you use this style:
    sum += ptr[index];

    Then you don’t have to deal with pointer increment.

    /Peter

    • Nigel Jones says:

      I often will use the style you suggest. However doing so precludes counting down if you use index as the loop variable. As a result, I can often get faster code using the pointer notation.

      • Lundin says:

        Why? I don’t see why this would be different from pointer notation:

        for(i=MAX; i!=0; i–)
        {
        sum += ptr[i];
        }

        Also, ptr[i] is so-called “syntatic sugar”. Behind the lines, the compiler will actually read that code as

        sum += ptr + i;

        ——

        As I sidenote, I’m against counting down myself. There is no doubt that down-counting loops are faster than up-counting ones on any known CPU.

        Still, I don’t use them. In my opinion such optimizations should be handled by the compiler’s optimizer. When chosing between readability and efficiency, readability should win in 99% of the cases.

        ——

        Regarding “Saks notation”, I’m sure he’s got plenty of good arguments about where to place the const keyword.

        However, the C committee has stated that the feature of placing storage-class specifiers (extern, static etc) anywhere in the declaration will be removed from the language in the future. Thus int static x; will not be allowed in the future. So for consistency reasons, why not use the same notation for type qualifiers (const volatile etc)?

        • Anonymous says:

          >Lundin says:
          >December 3, 2010 at 10:59 am
          >Why? I don’t see why this would be different from pointer notation:
          >
          >for(i=MAX; i!=0; i–)
          >{
          >sum += ptr[i];
          >}
          This code writes one element past the end of the ptr buffer, and does not touch the bottom (index=0) of the ptr buffer. That is the problem with counting down using an index. The terminating condition is harder to capture.

          • Lundin says:

            Maybe it does, maybe not 🙂

            You’re assuming 0-indexing. The problem is that you can’t compare against any other value than zero when downcounting an unsigned int, therefore you must use a 1-indexed array. Or alternatively something ugly like this:

            #define MSB_MASK_32 ( 1U << 31 )

            BOOL is_overflow (uint32 i)
            {
            i = i & (1U < 0;
            }

            for(i=MAX-1; !is_overflow(i); i–)

            I cannot quite see how this is a better alternative. These obfuscated examples are surely valid reasons to never write downcounting loops.

        • Eric Miller says:

          Hi Lundlin,

          You have a typo:

          sum += ptr[i];

          really compiles into something that looks like:

          sum += *(ptr + i);

          (your post omitted the pointer dereference)

  10. jilles says:

    For the available compiler optimizations, note that a const via a pointer does not create any optimization possibilities because it can be cast away (and if the coding standard does not permit this directly, it can be done by an intermediate cast to uintptr_t or a union; it is required to implement strchr(), for example). The only thing the C standard really forbids (undefined behaviour) is modifying an object that was declared const.

    Therefore, only const on an object with static storage duration tends to be an optimization (by assuming it does not change and frequently by placing the object into read-only memory).

    Any other const is really only documentation. However, it is better documentation than a comment because the compiler checks it.

  11. Vincent says:

    I was under the impression that in a function prototype, the position of the coast keyword matters.

    void func1( const uint32 * ptr){}

    Would suggest that the pointer will not be modified. Ie always points to the same address.

    Where

    void func2( uint32 * const ptr){}

    Will not change the content of the memory ptr points to.

    I maybe wrong, please comment.

    • Nigel Jones says:

      Correct. However it’s not just true for a function prototype, it’s true for all pointer declarations. Thus the following are all different:
      int const * ptr;
      int * const ptr;
      int const * const ptr;

      However the following are the same:
      int const * ptr;
      const int * ptr;

      • Ufuk DALLI says:

        I think you are making a mistake here, because;

        void func1( const uint32 * ptr){} : func1 is a function that takes a pointer to a const uint32 as parameter and returns void, implying the pointee is read-only and the pointer is read-write. This one is equal to void func1( uint32 const * ptr){}.

        void func2( uint32 * const ptr){} : func1 is a function that takes a const pointer to a uint32 as parameter and returns void, implying the pointer is read-only and the pointee is read-write.

        Please correct me if I am wrong.

  12. Eric says:

    Lively thread, here. …..

    So how do I declare and pass a constant array of pointers to structures that are not constant?

    struct Foo
    {
    int foo;
    };

    const struct Foo * array_of_foo[] —> mutable array of constant foo’s
    struct Foo const * array_of_foo[] —> same as above
    struct Foo * const array_of_foo[] —> error(?)
    struct Foo * array_of_foo const [] —> fuggeddaboutit
    struct Foo * array_of_foo[] const —> Hmm….

    ???

Leave a Reply