Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Initialize (almost) everything

3.00/5 (2 votes)
23 Sep 2010CPOL3 min read 11.3K  
Initialize (almost) everything

One of the things that I like most about programming is that there’s always something new to learn, even basic stuff. A while ago, I started to always initialize everything. This helped me avoid erratic bugs that are very difficult to diagnose; there is a performance penalty, but in general it’s negligible. Yesterday, a coworker showed me an interesting use of not initializing.

Just in case, most of this post only applies to C/C++; Java and .NET for example have automatic initialization. In native code, bugs that hide in uninitialized data are very bad guys; they show themselves in erratic ways, sometimes even only on optimized builds, what makes them even harder to debug. The cost of initialization is therefore almost always worth paying. That’s why I always initialize a stack variable on declaration and I always initialize memory immediately after allocation.

Trying to use an uninitialized variable generates a warning in most cases. If you're reading this and you're not building with all warnings on and the “treat warnings as errors” options, please go change your options now and then come back and continue reading. :D For the warning to be generated, you don't have to do things like passing the variable by reference in the code path, because in those cases the compiler loses track of what you’re doing; hence, you have to be careful on when and how to rely on this warning. In this case, we’re going to see a case when the warning can protect us and where we on purpose don’t initialize a variable on declaration.

This is a common error handling construction:

C++
HRESULT MyFunc()
{
    HRESULT hr = S_OK;
    ...
    if (AnError)
    {
        hr = E_FAIL;
        goto Error;
    }
    ...
    if (AnotherError)
    {
        hr = E_POINTER;
        goto Error;
    }
    ...
Error:
    ...
    return hr;
}

For religious people that think that goto is Satan’s work, here goes another construction. I personally think the following one is less clear and an unnecessary twist, but just in case:

C++
HRESULT MyFunc()
{
    HRESULT hr = S_OK;
    do
    {
        ...
        if (AnError)
        {
            hr = E_FAIL;
            break;
        }
        ...
        if (AnotherError)
        {
            hr = E_POINTER;
            break;
        }
        ...
    } while (FALSE);
    ...
    return hr;
}

A problem this code has, in both versions, is that if you forget to set hr in a code path the function will return S_OK; this can cause a hard to find bug. In error cases, you will probably generate logs, but a silent failure is very dangerous. A possible solution is initializing hr with an error value and setting it to S_OK after you know everything went OK:

C++
HRESULT MyFunc()
{
    HRESULT hr = E_UNKNOWN;
    ...
    if (AnError)
    {
        hr = E_FAIL;
        goto Error;
    }
    ...
    if (AnotherError)
    {
        hr = E_POINTER;
        goto Error;
    }
    ...
    hr = S_OK;
Error:
    ...
    return hr;
}

This one was better, if you forget to set hr you at least get an error. There is a better solution though. The sooner you catch a bug the better, so let’s catch this at compile time (I’ll write more about this good practice in a later post). To make the compiler help us, let’s introduce an exception to our initialization golden rule and write the code like this:

C++
HRESULT MyFunc()
{
    HRESULT hr;
    ...
    if (AnError)
    {
        hr = E_FAIL;
        goto Error;
    }
    ...
    if (AnotherError)
    {
        hr = E_POINTER;
        goto Error;
    }
    ...
    hr = S_OK;
Error:
    ...
    return hr;
}

If we follow the convention of not passing hr by reference, not aliasing it and so on (what is technically called “don’t do anything weird” ;) ) we’ll get a compiler warning if we forget to set hr in any code path. A warning is an error for me and I won’t be able to submit my code if it doesn’t build clean, hence I have a guarantee that I will in fact always set hr. Of course I can make another thousand mistakes like setting the incorrect value because of copy-pasting code, that’s why we have unit tests and all the other tests that a product has to clear to be shipped. But when we follow this good practice, there is at least one less little critter trying to escape from the tests and get into the customer’s home. :P

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)