Eight C++ programming mistakes the compiler won’t catch

C++ is a complex language, full of subtle traps for the unwary. There is an almost infinite number of ways to screw things up. Fortunately, modern compilers are pretty good at detecting a large number of these cases and notifying the programmer via compile errors or warnings. Ultimately, any error that is compiler-detectable becomes a non-issue if properly handled, as it will be caught and fixed before the program leaves development. At worst, a compiler-detectable error results in lost time while the programmer searches for a solution or workaround.

The dangerous errors are the ones that compilers are unable to detect. These errors are much less likely to be noticed, and can cause severe consequences such as incorrect outputs, corrupted data, and/or program crashes. As the size of a programming project increases, the complexity of the logic and large number of paths of execution can help obscure these bugs, causing them to appear only intermittently, making them particularly hard to track down and debug. Although this list may be mostly review to experienced programmers, the consequences of making one of these errors is also amplified due to the scale and commercial nature of the projects experienced programmers tend to work on.

These examples were all tested using Visual Studio 2005 Express using the default warning level. Your results may vary on other compilers. I highly recommend all programmers use the highest warning level possible!. Some items that may not be flagged as a potential problem at the default warning level may be caught at the highest warning level!

(Note: This article is part 1 in an intended series of articles)

1) Uninitialized variables

Uninitialized variables are one of the most devious mistakes commonly made in C++. Memory allocated to a variable in C++ is not cleared or zeroed upon allocation. Consequently, an uninitialized variable will have some value, but there is no way to predict what that value will actually be. Furthermore, the value of the variable may change each time the program is executed. This can result in intermittent problems, which are particularly hard to track down. Consider the following snippet:

if (bValue)
    // do A
    // do B

If bValue is uninitialized, it could evaluate to either true or false, and either branch could be taken.

In some basic cases, the compiler will be able to inform you of an uninitialized variable. The following causes a compiler warning on most compilers:

int foo()
int nX;
return nX;

However, other simple cases generally do not produce warnings:

void increment(int &nValue)

int foo()
int nX;
return nX;

The above may not produce a warning because the compiler typically doesn’t keep track of whether increment() assigns a value to nValue.

Uninitialized variables are even more likely to appear in classes, where member declaration are generally separated by from the constructor implementation:

class Foo
    int m_nValue;

    int GetValue() { return m_nValue; }

    // Oops, we forget to initialize m_nValue

int main()
    Foo cFoo;
    if (cFoo.GetValue() > 0)
        // do something
        // do something else

Note that m_nValue is never initialized. Consequently, GetValue() returns a junk value, and either branch may be executed.

New programmers often make the following mistake when declaring multiple variables:

int nValue1, nValue2 = 5;

The assumption being made here is that 5 is assigned to both nValue1 and nValue2, when in fact the value of 5 is only assigned to nValue2, and nValue1 is uninitialized.

Because uninitialized variables can evaluate to any value, which can cause the program to exhibit different behavior each time it is run, problems caused by uninitialized variables are particularly hard to find. One run, the program may work fine. The next time, it may crash. The after that, it may produce the wrong output.

To compound problems finding uninitialized variables, variable declared when running the program in a debugger are typically zeroed. This means your program may work fine every time when run in a debugger, but crash intermittently in release mode! If this is the case, an uninitialized variable is often the root of your problem.

2) Integer division

Most binary operators in C++ require both operands to be the same type. If the operands are of different types, one of the operands is promoted to match the type of the other.

In C++, the division operator can be thought of as two different operators: one that works on integer operands, and one that works on floating point operands. If the operands are of a floating point type, the division operator will return a floating point value:

float fX = 7;
float fY = 2;
float fValue = fX / fY; // fValue = 3.5

If the operands are of an integer type, the division operator will drop any faction and return an integer value:

int nX = 7;
int nY = 2;
int nValue = nX / nY; // nValue = 3

If one operand is an integer, and the other is a floating point value, the integer value will be promoted to a floating point type:

float fX = 7.0;
int nY = 2;
float fValue = fX / nY;

// nY is promoted to float, floating point division used
// fValue = 3.5

Many new programmers attempt to do the following:

int nX = 7;
int nY = 2;
float fValue = nX / nY;  // fValue = 3 (not 3.5!)

The underlying assumption here is that nX / nY will result in a floating point division because the result is being assigned to a floating point value. However, this is not the case. nX / nY is evaluated first, resulting in an integer value, which is then promoted to a float and assigned to fValue. However, by that point, the fraction has already been lost.

In order to force two integers to use floating point division, one of the values should be cast to a floating point value:

int nX = 7;
int nY = 2;
float fValue = static_cast<float>(nX) / nY; // fValue = 3.5

Because nX is being explicitly cast to a float, nY will be implicitly promoted to a float, which will cause the division operator to perform floating point division, resulting in a value of 3.5.

It is often hard to tell at a glance whether a given division operation is performing integer or floating point division:

z = x / y; // is this integer or floating point division?

However, using Hungarian Notation can help disambiguate the case and help prevent mistakes:

int nZ = nX / nY; // integer division
double dZ = dX / dY; // floating point division

One other interesting issue with integer division is that C++ does not define how to truncate the result when one operand is negative. Consequently, the compiler is free to truncate up or down! For example, -5 / 2 can evaluate to either -3 or -2, depending on whether the compiler rounds down or rounds toward 0. Most modern compilers round towards 0.

3) = vs ==

This one is an oldie but a goodie. Many beginning C++ programmers confuse the meaning of the assignment operator (=) with the equality operator (==). But even programmers who know the difference can make a typo that will have unintended results:

// if nValue is 0, return 1, otherwise return nValue
int foo(int nValue)
    if (nValue = 0) // TYPO!
        return 1;
        return nValue;

int main()
    std::cout << foo(0) << std::endl;
    std::cout << foo(1) << std::endl;
    std::cout << foo(2) << std::endl;

    return 0;

Function foo() is intended to return 1 if nValue is 0, otherwise return nValue. But due to inadvertently using the assignment operator instead of the equality operator, the program produces an unexpected result:


When the if statement in foo() is evaluated, nValue is assigned the value of 0. if (nValue = 0) evaluates the same way that nValue = 0; if (nValue) evaluates. Consequently, the if condition is false, which causes the else statement to return nValue, which was just assigned the value of 0!

Consequently, this function always returns 0.

Running a modern compiler at the highest warning level will cause it to issue a warning when an assignment is used in a conditional statement, or a note that the statement does nothing when an equality test is used instead of an assignment outside of a conditional. This is one issue that is essentially fixable -- if you use the higher warning levels.

4) Mixing signed and unsigned values

As mentioned in the section on integer division, most binary operators in C++ require both operands to be the same type. If the operands are of different types, one of the operands is promoted to match the type of the other.

This can lead to some very unexpected results when mixing signed and unsigned values! Consider the following case:

cout << 10 - 15u; // 15u is unsigned

One would expect the answer to be -5. However, because 10 is an signed integer and 15 is an unsigned integer, the type promotion rules come into effect here. The hierarchy used for type promotion in C++ looks like this:

long double (highest)
unsigned long int
long int
unsigned int
int (lowest)

Because the int operand is considered lower than the unsigned int operand, the int is promoted to an unsigned int. Fortunately, 10 is already a positive number, so the promotion does not cause our number to be interpreted any differently.

Thus, we effectively have:

cout << 10u - 15u

Here’s where the tricky part happens. Because both variables are unsigned integers, the result of the operation is also an unsigned integer! 10u - 15u = -5u. But unsigned variables can not hold negative numbers, and thus the -5 is interpreted as 4,294,967,291 (assuming 32 bit integers).

Consequently, the following program:

cout << 10 - 15u; // 15u is unsigned

prints 4,294,967,291, not -5.

This situation can come up in more obscure forms:

int nX;
unsigned int nY;

if (nX - nY < 0)
    // do something

Due to the type conversion, this if statement will always evaluate to false, which is clearly not what the programmer intends!

5) delete vs. delete[]

Many C++ programmers forget that there are actually two forms of the both the new and delete operators: a scalar version, and an array version.

Operator new is used to allocate scalar (non-array) data on the heap. If the object being allocated is a class type, the object’s constructor is called.

Foo *pScalar = new Foo;

The delete operator is used to destroy a scalar object that has been allocated using the new operator. If the object being destroyed is a class type, the object’s destructor is called.

delete pScalar;

Now consider the following snippet:

Foo *pArray = new Foo[10];

This snippet allocates an array of 10 Foo. Because the subscript [10] is placed after the int type specifier, many C++ programmers do not realize that operator new[] is being called to do the array allocation instead of operator new. Operator new[] ensures that the constructor is called for each object being constructed.

Conversely, to delete an array, the delete[] operator should be used:

delete[] pArray;

This ensures that the destructor for each object in the array is called.

If the delete operator is used on an array, only the first object will be destructed, and heap corruption can result!

6) Side effects in compound expressions or function calls

A side effect is a result of an operator, expression, statement, or function that persists even after the operator, expression, statement, or function has finished being evaluated.

Side effects can often be useful:

x = 5;

The assignment operator has the side effect of changing the value of x permanently. Other C++ operators with useful side effects include *=, /=, %=, +=, -=, <<=, >>=, &=, |=, ^=, and the infamous ++ and -- operators.

However, there are several places in C++ where the order of operations is undefined, and these can lead to inconsistent behavior. For example:

void multiply(int x, int y)
    using namespace std;
    cout << x * y << endl;

int main()
    int x = 5;
    std::cout << multiply(x, ++x);

Because the order of evaluation of the function parameters for multiply() is undefined, this could print 30 or 36, depending on whether x or ++x is evaluated first.

A slightly stranger example involving operators:

int foo(int x)
return x;

int main()
    int x = 5;
    std::cout << foo(x) * foo(++x);

Because the order of evaluation of the operands of C++ operators is undefined (for most operators -- there are a few exceptions), this could also print 30 or 36, depending on whether the left or right operand is evaluated first.

Also consider the following compound expression:

if (x == 1 && ++y == 2)
    // do something

The intent of the programmer is probably to say “if x is 1 and the pre-incremented value of y is 2, then do something”. However, if x does not equal 1, C++ uses short-circuit evaluation, which means that ++y never gets evaluated! Thus, y will only be incremented if x evaluates to 1, which is probably not what the programmer intended!

A good rule of thumb is to put any operator that causes a side effect in it’s own statement.

7) Switch statements without break

Another classic mistake that new programmers make is forgetting to use break to end a switch block:

switch (nValue)
    case 1: eColor = Color::BLUE;
    case 2: eColor = Color::PURPLE;
    case 3: eColor = Color::GREEN;
    default: eColor = Color::RED;

When the switch expression evaluates to the same value as the case label expression, execution starts at the matching case statement. Execution then continues until either the end of the switch block is reached, or a return, goto, or break statement is executed. Any other labels are ignored!

Consider what happens if nValue is 1 in the above program. Case 1 matches, so eColor is set to Color::BLUE. Evaluation proceeds to the next statement, which sets eColor to Color::PURPLE. The next statement sets it to Color::GREEN. And finally, it gets set to Color::RED.

In fact, this snippet ends up setting eColor to COLOR::RED no matter what the value of nValue is!

The correct way to write the above program is:

switch (nValue)
    case 1: eColor = Color::BLUE;  break;
    case 2: eColor = Color::PURPLE;  break;
    case 3: eColor = Color::GREEN;  break;
    default: eColor = Color::RED;  break;

The break terminates the case statement, thus causing eColor to retain the value that the programmer intended.

Although this is very basic switch/case logic, it is very easy to miss a break statement and end up with inadvertent fall-through.

8) Calling virtual functions in constructors

Consider the following program:

class Base
    int m_nID;
        m_nID = ClassID();

    // ClassID returns a class-specific ID number
    virtual int ClassID() { return 1; }

    int GetID() { return m_nID; }

class Derived: public Base

    virtual int ClassID() { return 2; }

int main()
    Derived cDerived;
    cout << cDerived.GetID(); // prints 1, not 2!
    return 0;

In this program, the programmer has called a virtual function inside the constructor of a base class, expecting it to resolve to Derived::ClassID(). It doesn’t -- and consequently, the program prints 1 instead of 2.

When an class that has been derived from a base classes is instantiated, the base class object is constructed before the derived class object. This is done because the derived class members may be dependent upon members of the base class already being initialized. Consequently, when the base object constructor is being executed, there is no derived object! It hasn’t been created yet. Thus, any call to a virtual function can only resolve to the level of the base class, not the derived class.

As pertains to this example, when the Base portion of cDerived is being constructed, the Derived portion does not exist yet. Thus, the function call to ClassID() resolves to Base::ClassID() (not Derived::ClassID()), which sets m_nID to 1.

Once the Derived portion of cDerived has been constructed, any calls to ClassID() on this object will resolve to Derived::ClassID() as anticipated.

Note that some other programming languages (such as C# and Java) will resolve virtual function calls to the most derived class even if the derived class has not been initialized yet! C++ differs in this regard, and does so for the programmer’s safety. That is not to say one way is necessarily better than the other, but merely to denote that different languages may have different behaviors.


As this is the first article in this series, I thought it appropriate to start with some of the more basic issues that new programmers will encounter. Future articles in this series will tackle programming mistakes of an increasingly complex nature.

Regardless of a programmer’s experience level, mistakes happens, whether through lack of knowledge, a typo, or general carelessness. Being aware of which issues are most likely to cause trouble can help reduce the probability that they will cause trouble. While there is no substitute for experience and knowledge, good unit testing can help catch many of these before they get buried under layers of other code!

Related articles:

15 comments to Eight C++ programming mistakes the compiler won’t catch

  • Aakash

    In Uninitialized variables section, in the class example,
    "return m_bValue"
    must be
    "return m_nValue"

  • uvramana

    Great sir .Thank you

  • Ashoka

    Hello Alex,

    From the point 8 as below:

    By the time we use "cout << cDerived.GetID()" the object is already constructed with first line. So I am not able to understand still the code resolves to Base class member. Why ?

    • Alex

      GetID() is a base class only member function that simple returns the value of m_nID, so the question is -- why does m_nID have the value of 1 instead of 2?

      The answer is because in the constructor of Base, we set m_nID = ClassID(), where ClassID() is a virtual function. Because cDerived is a Derived object, we were expecting ClassID() to resolve to Derived::ClassID(), which would have returned 2. However, when the Base portion of the object is being constructed, there is no Derived portion of the object yet, so the call to ClassID() resolves to Base::ClassID(), which returns 1. Thus, m_nID gets assigned 1 instead of 2 like we were expecting.

      The call to GetID() just reflects this.

  • Paul

    Hi Alex,
    is there a reason why compiler does NOT warn you that you’ve made ‘Integer division’ mistake? I haven’t found any flag to turn this on. Is it so difficult for compiler to detect it? I do not see any sufficient reasons (maybe there are some) to write  ‘float = int / int’, so I’d expect some warning.

    • Alex

      I’m not sure why compilers don’t warn about this, since integer / integer always has the potential to lose some data. Maybe one reason why: how would you declare that yes, I fully intend to do this?

  • Sergey

    Thank you, for great work. Just wanted to inform the visitors of this page that free code analyzing tool CppCheck ( can find all uninitialized variables mentioned in first chapter “Uninitialized variables”.

  • Veni Yanda

    Thank You Alex,For your great job!! I am from Indonesia, This course are very easier to undertstand than so many c++ text books that i’ve downloaded so far. Keep it simple, easy to understand, and directly to the point.

  • Akhunzaada

    Howdy, Alex!

    Thanks for the great work! I love your tutorial series. This article is very informative. I got a question here -- in Mixed signed and unsigned values.

    To my understand I think by promotion you mean either to type cast or change it’s type to another. But, is it done by the compiler or by the programmer manually?

    >Due to the type conversion, this if statement will always evaluate to false, which is clearly not what the programmer intends!

    Can you clarify this also… What you mean by type conversion?
    In this snippet: Are the types in the if body different as they were declared? Or both changed to the same type?

    Hope to get a response soon!

  • Great article… you can also have a look at this article 5 Mistakes Beginners Make with Pointers in C

  • spoonlicker

    I want to know how to make a 3D commercial-quality game in machine language.

  • spleen2060

    because this are semantic not syntax error

  • DannyB

    Here are some more:

    #define while if // speeds up code
    #define struct union // saves memory

  • sergk

    Sorry, not virtual, but abstract.

  • sergk

    I’d like to add nasty one - Calling any self-methods from destructors.
    Eventually this could lead to “pure virtual function calls”. It is very hard to track down if there is a virtual method in call-chain.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter