Unspecified Behavior in CPA Sample Exam Question 11

This blog post was written sometime in 2018 or 2019.

The sample exam questions publicly available on the C++ Institute website for CPA contains the following question:

Question 11

What is the output of the following program?

#include <iostream>
using namespace std;
class A {
public:
  A() { a.a = a.b = 1; }
  struct { int a,b; } a;
  int b(void);
};
int A::b(void) { int x=a.a;a.a=a.b;a.b=x; return x; };
int main(void) {
  A a;
  a.a.a = 0;
  a.b();
  cout << a.b() << a.a.b << endl;
  return 0;
}

A. The program will cause a compilation error
B. 10
C. 01
D. 11

So, this program is composed of a class called A, and the main function.

The class A contains a struct called a, which contains 2 ints, a.a and a.b.

Upon creation, the constructor of A initializes its a.a and a.b both to 1. So at this point, a.a.a and a.a.b are both 1.

Next, we set a.a.a to 0. So at this point, a.a.a is 0 and a.a.b is 1.

Now we call a.b(). The b function first sets a variable x to a, then sets a.a to a.b, finally it sets a.b to x and returns x. It just swaps the values of a.a and a.b and returns the initial value of a.a. So at this point, a.a.a is 1 and a.a.b is 0.

Now we come to the interesting part, which is this line:

cout << a.b() << a.a.b << endl;

For readers who may not be familiar with C++, here is a brief description of the C++ order of evaluation:

Order of evaluation of any part of any expression, including order of evaluation of function arguments is unspecified (with some exceptions listed below). The compiler can evaluate operands and other subexpressions in any order, and may choose another order when the same expression is evaluated again.

There is no concept of left-to-right or right-to-left evaluation in C++. This is not to be confused with left-to-right and right-to-left associativity of operators: the expression a() + b() + c() is parsed as (a() + b()) + c() due to left-to-right associativity of operator+, but the function call to c may be evaluated first, last, or between a() or b() at run time:

#include <cstdio>
int a() { return std::puts("a"); }
int b() { return std::puts("b"); }
int c() { return std::puts("c"); }
void z(int, int, int) {}
int main() {
   z(a(), b(), c());       // all 6 permutations of output are allowed
   return a() + b() + c(); // all 6 permutations of output are allowed
}

So now let’s revisit our expression above:

cout << a.b() << a.a.b << endl;

According to the C++ standard, in this expression, either a.b() or a.a.b could be evaluated first. The order of evaluation is strictly unspecified. What does that mean, practically speaking? Let’s consider the two possible cases:

Case 1. a.b() is evaluated first. In this case, the values of a.a and a.b are swapped and the initial value of a.a is returned. Recall that before this line is called, a.a.a is 1 and a.a.b is 0. Thus, a.b() returns 1 and swaps the values of a.a.a and a.a.b. So now the value of a.a.b is 1. Which means the output should be 11.

Case 2. a.a.b is evaluated first. Recall that before this line is called, a.a.a is 1 and a.a.b is 0. Thus, the second number to be printed will be 0. Now, a.b() returns the initial value of a.a.a, which is 1. Which means the output should be 10.

So far, this is all theory (reasoning as per the C++ standard, which is what any good C++ programmer should do). So let’s look at what compilers in real life say.

GCC 6.3.0 says it’s 10

Clang 5.0.0 says it’s 11

Thus, Clang 5.0.0 has decided to evaluate the expression as per case 1 whilst GCC 6.3.0 has decided to evaluate the expression as per case 2.

Interestingly, GCC 7.1.0 also says it’s 11. In fact, all the versions of GCC that I tested, from 7.1.0 to 10.0.0 all agree that the output should be 11.

Guess what the C++ Institute thinks is the correct answer for this question?

10 (answer B)

That is to say, the C++ Institute’s official answer disagrees with the current versions of both GCC and Clang.

I emailed the C++ Institute about this but received no reply.

I have some other gripes against the CPA certification (e.g they teach an outdated version of C++ e.g. they still teach the pre-C++11 meaning of the auto keyword), but I won’t go into them here.

But I still think the CPA has some value for beginners. The process that I went through above to reason about the code is a simplified version of desk checking (which involves writing down tables with line numbers, variables, conditions, and code), which is a useful technique for reasoning about hard-to-understand bits of code. In real life, you will often encounter code that is hard to understand, and being able to figure out exactly what they do is quite useful. The CPA questions require beginners to carefully reason about code, which is useful.