munotes®

Increment and Decrement Operators

Chapter Seventeen

Syllabus topic 2, "Type of operators: Arithmetic operators, relational and logical operators, Increment and Decrement operators, assignment operators, the conditional operator, Assignment operators and expression, Precedence and order of Evaluation Block Structure, Initialization, C Preprocessor"

Pages 80 to 84 of 222

In one line

++ adds one and -- subtracts one, and where you put the operator decides whether the expression's value is taken before the change or after it.

Why the operators exist

i = i + 1 names i twice. That is fine for a simple variable and it stops being fine the moment the thing being stepped is longer: counts[student][paper] = counts[student][paper] + 1 gives a reader two long expressions to compare character by character, and gives the writer two chances to mistype one.

counts[student][paper]++ names it once. That is the whole argument, and it is a good one. The operators came from B, and B took them from the fact that the PDP-11 had an instruction for exactly this, but they have outlived that reason.

Prefix and postfix

There are four forms and they are two operators each used two ways.

FormNameWhat it doesWhat the expression is worth
++ipre-incrementadds 1 to ithe new value
i++post-incrementadds 1 to ithe old value
--ipre-decrementsubtracts 1 from ithe new value
i--post-decrementsubtracts 1 from ithe old value

The way to remember it: read the operator in the order it is written. ++i is "increment, then use i". i++ is "use i, then increment".

The listing below is the one every textbook prints, and gcc warns about every line of it. It is shown for exactly that reason.

#include <stdio.h>

int main(void)
{
    int i;

    i = 5;
    printf("i = 5; printf with ++i gives %d, and i is now %d\n", ++i, i);

    i = 5;
    printf("i = 5; printf with i++ gives %d, and i is now %d\n", i++, i);

    i = 5;
    printf("i = 5; printf with --i gives %d, and i is now %d\n", --i, i);

    i = 5;
    printf("i = 5; printf with i-- gives %d, and i is now %d\n", i--, i);
    return 0;
}
textbook.c: In function ‘main’:
textbook.c:8:66: warning: operation on ‘i’ may be undefined [-Wsequence-point]
    8 |     printf("i = 5; printf with ++i gives %d, and i is now %d\n", ++i, i);
      |                                                                  ^~~
textbook.c:11:67: warning: operation on ‘i’ may be undefined [-Wsequence-point]
   11 |     printf("i = 5; printf with i++ gives %d, and i is now %d\n", i++, i);
      |                                                                  ~^~
textbook.c:14:66: warning: operation on ‘i’ may be undefined [-Wsequence-point]
   14 |     printf("i = 5; printf with --i gives %d, and i is now %d\n", --i, i);
      |                                                                  ^~~
textbook.c:17:67: warning: operation on ‘i’ may be undefined [-Wsequence-point]
   17 |     printf("i = 5; printf with i-- gives %d, and i is now %d\n", i--, i);
      |                                                                  ~^~

It ran anyway, and on this compiler it printed the numbers the textbooks give:

munotes.in80

Increment and Decrement Operators

i = 5; printf with ++i gives 6, and i is now 6
i = 5; printf with i++ gives 5, and i is now 6
i = 5; printf with --i gives 4, and i is now 4
i = 5; printf with i-- gives 5, and i is now 4

Each of those printf calls passes i twice, once through the operator and once plainly, and the order in which printf's arguments are evaluated is unspecified. The program above is therefore relying on something it should not, and it is shown here only because it is what every textbook shows. The next section is the honest version, and it is the one to learn from.

The honest version: one change per statement

#include <stdio.h>

int main(void)
{
    int i = 5;
    int taken;

    taken = ++i;
    printf("after taken = ++i : taken is %d and i is %d\n", taken, i);

    i = 5;
    taken = i++;
    printf("after taken = i++ : taken is %d and i is %d\n", taken, i);

    i = 5;
    taken = --i;
    printf("after taken = --i : taken is %d and i is %d\n", taken, i);

    i = 5;
    taken = i--;
    printf("after taken = i-- : taken is %d and i is %d\n", taken, i);
    return 0;
}
after taken = ++i : taken is 6 and i is 6
after taken = i++ : taken is 5 and i is 6
after taken = --i : taken is 4 and i is 4
after taken = i-- : taken is 5 and i is 4

In every case i ends up as 6 or 4. The only thing that differs is what was handed to taken, and that is the entire content of the topic.

When the difference does not matter

i++;        /* as a statement on its own */
++i;        /* identical effect          */

As a complete statement, nothing uses the expression's value, so the two forms do exactly the same thing. Both compile to the same instruction. Use whichever you find clearer; this book uses i++ in a for loop because that is what every C program does.

The claim that ++i is faster than i++ is folklore. It was never true for an int on any compiler you will use. It can matter in C++ for a large object with an overloaded operator, and C has no such thing.

When the difference matters: walking an array

This is the real use, and it is worth seeing once now and again in chapter 37.

#include <stdio.h>

int main(void)
{
    int a[5] = {10, 20, 30, 40, 50};
    int i = 0;
    int sum = 0;

    while (i < 5) {
        sum = sum + a[i++];         /* use a[i], THEN step i */
    }
    printf("sum with a[i++] is %d, and i ended at %d\n", sum, i);

    i = 0;
    printf("the first three, taken with a[i++]: ");
    printf("%d ", a[i++]);
    printf("%d ", a[i++]);
    printf("%d\n", a[i++]);
    return 0;
}
munotes.in81

Increment and Decrement Operators

sum with a[i++] is 150, and i ended at 5
the first three, taken with a[i++]: 10 20 30

Each printf above is a separate statement, so each i++ is safely sequenced against the others. That is the discipline: one modification of a variable per statement.

The undefined-behaviour question, and how to answer it

Question banks ask for the value of things like this:

i = 5;
j = i++ + ++i;
printf("%d %d\n", i, j);

There is no answer. The standard says that if two side effects on the same object are unsequenced, the behaviour is undefined, and there are two modifications of i here with nothing sequencing them. Undefined behaviour means the standard places no requirement on the result at all: the program may print anything, may print something different on the next compiler, and is not required to be consistent.

gcc says so itself. This listing is wrong on purpose:

#include <stdio.h>

int main(void)
{
    int i = 5;
    int j;

    j = i++ + ++i;
    printf("i is %d and j is %d, and neither is guaranteed\n", i, j);
    return 0;
}

This book does not print what that program produced, and will not. Its result is undefined, so any number printed here would be a claim about a program the standard makes no promise about, and a reader would reasonably take it for the answer. The compiler's warning is the whole of what can honestly be shown:

unsequenced.c: In function ‘main’:
unsequenced.c:8:10: warning: operation on ‘i’ may be undefined [-Wsequence-point]
    8 |     j = i++ + ++i;
      |         ~^~

The answer to write in an examination, which is correct and complete:

The expression modifies i twice without an intervening sequence point, so by clause 6.5 of the standard the behaviour is undefined. The standard imposes no requirement on the result, so no value can be given, and different compilers will produce different answers. Written correctly as two statements, j = i++; j = j + ++i; the result is defined.

Some examiners want the number their own textbook prints. If you are certain that is what is wanted, give the textbook value and add the sentence above. The sentence cannot be marked wrong and it is the difference between having memorised an answer and understanding one.

Which expressions are safe

ExpressionSafe?Why
i++;YesOne modification, full statement
a[i++] = 0;Yesi modified once
x = i++ + 1;Yesi modified once
printf("%d\n", i++);Yesi modified once
x = i++ + i++;Noi modified twice, unsequenced
a[i] = i++;Noi read and modified, unsequenced
printf("%d %d\n", i++, i);NoArgument order unspecified
i = i++;NoTwo modifications of i
x = i++ && i++;Yes&& sequences its operands
munotes.in82

Increment and Decrement Operators

The last row is the exception that makes the rule clear: &&, | |, the comma operator and the conditional operator ?: all impose an order, so they sequence their operands. Everything else does not.

The one rule to take away

Modify a variable at most once in a statement, and do not also read it elsewhere in that statement. Follow it and you will never meet this problem. Break it and no amount of care about prefix and postfix will save you.

What this does NOT mean

++ does not work on a constant. 5++ does not compile. The operand must be something that can be assigned to.

++i is not "faster". For an int the two forms compile identically.

Postfix does not mean "later". The increment happens as part of evaluating the expression, not at the end of the statement. What is postponed is nothing; what differs is which value the expression yields.

Undefined behaviour is not "whatever your compiler does". It is a licence for the compiler to do anything, including assuming the case cannot arise and optimising on that basis. A program with undefined behaviour cannot be reasoned about at all.

i = i + 1 is not worse than i++. It is longer and perfectly correct. Prefer ++ where the thing being stepped is long enough that repeating it invites a mistake.

Quick revision

  • ++ adds one, -- subtracts one.
  • Prefix ++i yields the new value; postfix i++ yields the old.
  • Read the operator in the order written: ++i is increment-then-use; i++ is use-then-increment.
  • As a whole statement the two forms are identical in effect.
  • Modifying a variable twice in one statement is undefined behaviour, not a puzzle.
  • &&, | |, ?: and the comma operator sequence their operands; nothing else does.
  • The operand must be assignable: 5++ does not compile.
  • -Wall reports an unsequenced modification, and it is right.

Test yourself

1. After int i = 10; int j = i++; what are i and j?

i is 11 and j is 10. Postfix yields the old value.

2. After int i = 10; int j = ++i; what are i and j?

Both 11. Prefix yields the new value.

3. Is there a difference between i++; and ++i; as complete statements?

No. The value of the expression is discarded, so both simply add one.

munotes.in83

Increment and Decrement Operators

4. What is the value of j after int i = 5; int j = i++ + ++i;?

The question has no answer. i is modified twice with nothing sequencing the two, so the behaviour is undefined and the standard permits any result.

5. Is a[i] = i++; safe?

No. i is both read, to index the array, and modified, and the two are unsequenced. Write a[i] = i; i++;.

6. Why is x = i++ && i++; safe when x = i++ + i++; is not?

&& is guaranteed to evaluate its left operand fully, with a sequence point, before the right. Addition imposes no such order.

7. Rewrite sum = sum + a[i++]; without the increment operator.

sum = sum + a[i]; i = i + 1;

What can be asked on this, and how to answer it

"Explain the increment and decrement operators with examples." Give the four forms in a table with what each yields, the read-in-order rule, and a two-line program showing taken = i++ against taken = ++i. Add that as a standalone statement the two are identical, because that is the part most answers leave out.

"Distinguish between prefix and postfix increment." Prefix increments first and yields the new value; postfix yields the old value and then increments. In both cases the variable ends one larger. Give int i = 5; j = ++i; against int i = 5; j = i++; with the values.

"What is the output of i = 5; printf("%d %d", i++, ++i);?" Say that the behaviour is undefined: i is modified twice in one expression with nothing sequencing the two, and the order of evaluation of function arguments is unspecified besides. No value can be given, and different compilers differ. Then give the correct rewrite.

"Can ++ be applied to an expression like (a + b)?" No. The operand must be a modifiable object, and a + b is a value, not a place. (a + b)++ does not compile.

munotes.in84

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.

Report or request
Done!