Pre-increment or post-increment in C/C++
Most C or C++ programmers are completely clear about the behavior of the ++ operator [and --] and the result of expressions that employ it. However, what if the return value is unused? Does it matter whether the code uses ++i or i++? This article looks at the finer points of compiler code generation and why that choice might matter.
Although any C programmer could easily learn another programming language and quickly become proficient in it, C continues to be the dominant language for embedded software programming. One reason for this is the large reservoir of expertise in the language, and most embedded C programmers understand the low-level nuts and bolts of the language. However, as embedded developers are always concerned with writing efficient code and minimizing overheads, being “proficient” is not enough.
There are still facets that may come as a surprise, even to those with years of experience.
A case in point is the pre-increment/post-increment problem. Not long ago, I was working on a conference paper about using C++ for embedded applications. I sent the paper out to the embedded compiler and programming language experts on the Mentor Embedded development tools team and asked them to review my work. As expected, I got some detailed and helpful feedback, but I was left wondering about one particular suggestion. I had written something like this:
for (i=0; i<10; i++)
This seemed straightforward, but I was told that in terms of C++ style, it would be better to write this:
for (i=0; i<10; ++i)
I was mystified. I thought that I was entirely clear about the two forms of the ++ operator.
i++ and ++i
We are all familiar with the ++ operator (and its sibling --). Applying this operator increments the variable. Well, that is what appears to happen. Let’s look more closely.
++ is really a pair of operators: pre-increment and post-increment. The former increments the value of a variable and returns the resulting value; the latter increments the value of the variable and returns the value prior to the increment. So, if a variable x initially contains the value 3, the following expressions yield different results:
y = ++x; // y==4, x==4
y = x++; // y==3, x==4
The effect on x is the same in both cases, but y gets a different value.
This is all straightforward. The programmer needs to be careful to use the correct operator to achieve the required result in an expression.
Throwing away the result
A C program is built by formulating a number of expressions, each of which yields a result. However, an expression by itself cannot be executed. To create an executable unit of code, an expression must be converted into a statement by adding a semicolon at the end. Here are two statements:
They both achieve the same result: the variable x is incremented. Any value that the expressions yielded is lost. So, from a programmatic point of view, the two statements are identical.
Consider how each version of the operator may work. Pre-increment simply performs the operation on the variable and returns its value. Post-increment requires that the previous value be retained somewhere, ready for return, so some additional storage may be required. Thus, the two statements are potentially able to generate different code. The pre-increment version is clearly preferable.
Imagine that we were defining the int data type (only we will call it Integer) from scratch in C++. We might have something like this:
x_ += 1;
const Integer operator++(int)
Integer temp = *this;
The additional storage for the post-increment, temp, is clear.
In a real implementation of a complex data type (a class), the overhead may be much greater than just the additional word of storage and a little extra code.
Of course, a smart compiler would almost certainly pick this up and optimize the unnecessary storage away. It is, however, much better to understand the semantics of the language and write defensively – code exactly the functionality that you intend and do not rely on a compiler to make up for your shortcomings.
What Does “Increment” Mean?
Having dabbled with C++ here, we are reminded that operators can be redefined to do anything - although taking advantage of this capability and creating operators that perform functions radically different from standard is very bad programming practice.
However, it is not necessary to stray quite so far to see some interesting behavior from the ++ operator. Look at this code:
ptr = (int *)0x8000;
What value does ptr end up with? Many programmers would assume 0x8001, as there is a perception that, in C/C++, “increment” means “add 1”. But that is not the real meaning of the word – it really means “advance the value in a meaningful way”. So, the actual answer (assuming 32-bit integers) is 0x8004. The compiler knows how many bytes an int occupies – 4 – and advances the pointer accordingly. Even though, on the surface, this seems counter-intuitive, it would make sense if you were stepping a pointer through an array of integers.
So, again the behavior of this seemingly simple operator is more complex than might be assumed.
Colin Walls has over thirty years experience in the electronics industry, largely dedicated to embedded software. A frequent presenter at conferences and seminars and author of numerous technical articles and two books on embedded software, Colin is an embedded software technologist with Mentor Embedded [the Mentor Graphics Embedded Software Division], and is based in the UK. His regular blog is located at: blogs.mentor.com/colinwalls. He may be reached by email at firstname.lastname@example.org.
Currently no items