Dynamic vs static typing debates are often passionate. Probably too passionate to be totally objective. During one of these discussions I was involved in, a well respected senior engineer have made an interesting statement:

My lisp program crashed after hours of computation right before delivering the result due to a missing method runtime error. This would’ve NEVER happened in a statically typed language. For instance, this would’ve never happened in C++ using the latest Microsoft C++ compiler. With all the static analysis it performs, these errors are prevented. You are protected by the compiler!

Well, I don’t know how good is the MS compiler compared to other C++ compilers. Still, we’ll show how ridiculously easy it is to fool it and have the same missing method crash.

We will be using Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80×86, with “enable all warnings” and “treat warnings as errors” flags activated. We will also activate the “Enable Code analysis for C/C++ on Build” option in the “Code Analysis” project properties section and we will use “Microsoft All Rules” as a rule set.
In the code samples below, we will stick to ISO C++ with no third party libraries usage. Not even the standard C++ library.

Case 1 : Dispatch table corruption

Every C++ developer has run into this problem at least once in his life. An object gets deleted but the program keeps, somehow, a reference to it. Everything goes fine until you try to call a virtual method on this object : Boom! You’re most likely going to have a memory protection error followed by a crash. Of course, this will not happen during development or QA testing. This will happen in production when your application is most needed :).

Here is a snippet that illustrates the problem.

struct Base
{
    virtual void Do() =0;
};

struct Derived : public Base
{
    virtual void Do() {}
};

void main()
{
    Derived *p = new Derived();
    delete p;
    p->Do();
}

Derived class overrides and implements an abstract pure virtual method defined in class Base. In main(), we create a Derived object on the heap; we delete it and then we try calling the virtual method. The compiler compiles this program with no single warning. Static analysis detects nothing either. However, at runtime, it’s a different story. The dispatch table used for method resolution at runtime is not valid anymore after the object is delete. Calling the virtual method will amount to accessing a memory space that we’ve just disposed; hence a runtime error.

Memory corruption is unfortunately quite common in C++ and the compiler just can’t anything about it. This is why C++ developers use tools like valgrind and purify. They also use smart pointers and/or disciplined memory usage patterns to help containing these problems. You just can’t rely on the compiler.

Case 2 : Virtual method call from constructor

Memory corruption is not mandatory to have our missing method runtime error. Let’s have a look at the following snippet:

struct Base
{
    Base() { Do(); }
    void Do() { ReallyDo(); }
    virtual void ReallyDo() =0;
};

struct Derived : public Base
{
    virtual void ReallyDo() {}
};

void main()
{
    Derived d;
}

This program will crash complaining about a “pure virtual function call”. To understand what’s happening here, we must recall that objects are constructed top down in C++: from ancestors down to children classes. To construct the object d in main(), Base is built first. Base constructor is called, it calls method Do() that calls ReallyDo(). At this point, Derived is not built yet and thus the virtual table doesn’t contain yet the address to the correct virtual method. Therefore, the call to ReallyDo() fails.

To sum up,  the program is apparently calling an abstract (not implemented) method and, again, the compiler and the static analysis engine didn’t see it coming.

But wait, does this error really happen in real life? We’ve only provided toy programs to reproduce it after all. Well, the answer is Yes, it does happen even in widely used and heavily tested applications. Here is a screenshot of a crash I had recently using Adobe Acrobat Reader.

Conclusion

The compiler cannot help you write better code or avoid bugs. This is an urban legend that, unfortunately, some developers and managers keep propagating. Probably because of the false sense of security that a "Build succeeded " compiler message gives. If you are a manager and you want your team to write better quality code, try to invest a bit more on testing, code reviews, setting up best practices etc. You are Not protected by the compiler.