C++ Is Not C#!

And it’s not Java either.

Over the last 12 years I coded first in Java and then in C#. After switching back to coding in C++, I have sometimes written code that works perfectly if translated to Java or C#, but is full of bugs in C++. I will use the following C++ program as a starting point and then refactor it to show a number of problems:

#include <cstdint>
#include <iostream>

struct A
{
 uint32_t* pIntValue;
 float floatValue;
 float* pFloatValue;
};

void printA(const A& a)
{
 std::cout << "intValue: " << *a.pIntValue << ", floatValue: " << a.floatValue;
 std::cout << ", *pFloatValue: " << *a.pFloatValue <<'\n';
}

int main()
{
 uint32_t val1 = 6;
 A anA;
 anA.pIntValue = &val1;
 anA.floatValue = 100.3F;
 anA.pFloatValue = &anA.floatValue;
 printA(anA);
 std::cout << "Press a character key, then Enter to terminate\n";  
 char c;  
 std::cin >> c;
 return 0;
}

Running this program gives this output:

*pIntValue: 6, floatValue: 100.3, *pFloatValue: 100.3

as expected. Now I will factor out the creation and setting of the A struct (anA). I create a new function called getA:

A& getA(uint32_t anInt)
{
	float aFloat = 100.3F;
	A a;
	a.pIntValue = &anInt;
	a.floatValue = aFloat;
	a.pFloatValue = &aFloat;
	return a;
}

and modify main to call it:

int main()
{
	uint32_t val1 = 6;
	A anA = getA(val1);
	printA(anA);
	std::cout << "Press a character key, then Enter to terminate\n";
 	char c;
 	std::cin >> c;
	return 0;
}

If I wrote the equivalent code in C# or in Java, I should get the same result as above. But running this C++ program produces:

*pIntValue: 13630244, floatValue: 100.3, *pFloatValue: -1.07374e+08

which is obviously not what I wanted. How many bugs can you find? Hint: there are at least 3.

And here they are:

  1. getA returns a reference to the A struct created inside the function. In this specific program that is not a problem, but will probably be in more complex programs. At least the VC++ compiler warns about it.
  2. The argument is passed into getA by value. This makes a copy on the stack of the value being passed in. Once the program returns from getA, the stack pointer is repositioned to before the call to getA, and any storage in the stack may be overwritten in the next function call.
  3. aFloat is defined inside getA and is therefore stored on the stack. getA stores the value of aFloat in the structure, and also stores the address of aFloat in the structure. We have the same problem here as for the argument passed by value: after getA returns, the value on the stack can be overwritten, and therefore, the value pointed to will not be correct.

We see the results in the output.

Ok, so why is there no problem in C# or Java? Every variable is a reference counted object, even integer and float variables. So, for example, in getA, aFloat is created as an object on the heap and its reference count is set to 1. When pFloatValue is set, the reference count is incremented. Upon return from getA, the reference count for aFloat is decremented, but the count is not 0, so aFloat still exists on the heap, and therefore the address is still valid. Similarly, arguments to functions are passed as references with the reference count being incremented, and not as copies. Setting pIntValue in the struct again increments the reference count. Both anInt and aFloat remain greater than 0 until anA in main is destroyed. Only then do the reference counts for anInt and aFloat get reduced to 0 and become invalid.

So, let’s fix up the code:

A getA(uint32_t& anInt, float& aFloat)
{
	A a;
	a.pIntValue = &anInt;
	a.floatValue = aFloat;
	a.pFloatValue = &aFloat;
	return a;
}

int main()
{
	uint32_t val1 = 6;
	float aFloat = 100.3F;
	A anA = getA(val1, aFloat);
	printA(anA);
	std::cout << "Press a character key, then Enter to terminate\n";
 	char c;
 	std::cin >> c;
	return 0;
}

Note that I made the following changes:

  1. The struct, a, defined in the function is now returned by value;
  2. The definition of aFloat is moved to main.
  3. Both the integer and float values are passed into getA as references. This ensures that the addresses remain valid even on return from getA.

The output from the program is:

*pIntValue: 6, floatValue: 100.3, *pFloatValue: 100.3

as it was before the refactoring.

These problems showed up a lot in my code after I had returned to C++, but at least I have learned where to look.