Rusty Russell's Coding Blog | Stealing From Smart People

Nov/11

21

The Power of Undefined Values

Tools shape the way we work, because they change where we perceive risk when we write code.  If common compilers warn about something, I’ll code in a way that will trigger it in case of mistakes.  eg: instead of:

    int err = -EINVAL;
    if (something())
         goto out;
    err = -ENOSPC;
    if (something_else())
         goto cleanup_something;
...
cleanup_something:
    undo_something();
out:
    return err;

I would now set err in every branch:

    int err;
    if (something()) {
        err = -EINVAL;
        goto out;
    }
    if (something_else()) {
        err = -ENOSPC;
        goto out;
    }

Because when I add another clause to the initialization and forget to set err, gcc will warn me about it being uninitialized.  This bit me once, and it can be hard to spot the problem when you’re only reviewing a patch, not the code as a whole.

These days, we have valgrind, and despite its fame as a use-after-free debugger, it really shines at telling you when you rely on the results of an uninitialized field.  So, I’ve adapted to lean on it.  I explicitly don’t initialize structure members I don’t use in a certain path.  I avoid calloc(): while 0 is often less harmful than any other value, I’d much rather know that I’ve thought about and set up every field I actually use.  When changing code this is particularly important, and I spend a lot of my time changing code.  I have even changed to doing malloc() in some cases where I previously used on-stack or file-scope variables.  Valgrind doesn’t track on-stack usage very well, and static variables are defined to be zeroed, so valgrind can’t tell when I wander into the weeds.  I think these days, that’s a misfeature.

So, if I were designing a C-like language today, I’d bake in the concept of undefined values, knowing that the tools to leverage it are widely available.  10 years ago, I’d have said 0-by-default is safest, but times change.  I think Go chose wrong here, but it may not be as bad as C for other reasons.  I’d have to code in it for a few years to really tell.

RSS Feed

5 Comments for The Power of Undefined Values

Anonymous | November 21, 2011 at 8:47 pm

While I agree with the use of tools to check for common problems, and I find valgrind awesome for finding issues in C code, in general I’d prefer to have languages that make it possible to statically check as much as possible at compile time rather than at runtime.

Andreas | November 21, 2011 at 8:51 pm

Hi Rusty. To get valgrind complain about things going wrong on the stack, you can use client requests, see http://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq

in C++ that’s you can just set the whole object to being undefined in the constructor by using

VALGRIND_MAKE_MEM_NOACCESS(this, sizeof(*this));

to uncover problems more quickly, you may also make sure that a value is defined before an if statement is hit by

VALGRIND_CHECK_MEM_IS_DEFINED(&value, sizeof(value));

John Regehr | November 22, 2011 at 2:22 am

I generally agree with you, but neither compiler warnings nor Valgrind is reliable. The compiler can fail to warn about use of uninitialized data when that data goes through function calls or is pointed to. Valgrind only sees the binary, so it fails when the use of uninitialized data has been optimized away, and of course the compiler is trying very hard to do that. It would be excellent if it was guaranteed that either GCC or Valgrind would tell you about any given problem, but even that is not quite true — and it’s a very difficult guarantee to make without (as you suggest) baking it into the language design.

Perl actually comes closest to getting this right, out of languages I commonly use. Far from perfect, but not bad.

Author comment by rusty | November 22, 2011 at 12:12 pm

Andreas: the valgrind-specific hooks leave me cold. But you can malloc a single byte and memset that to a structure for same effect without requiring any valgrind headers to compile. Unfortunately, either was is a significant extra burden which reduces the “just works” factor which makes valgrind so awesome.

John: exactly true. I try to develop habits that increase the chance of spotting errors, but it’s far from perfect.

Andreas | November 24, 2011 at 1:32 am

> Andreas: the valgrind-specific hooks leave me cold. But you
> can malloc a single byte and memset that to a structure for
> same effect without requiring any valgrind headers to
> compile. Unfortunately, either was is a significant extra
> burden which reduces the “just works” factor which makes
> valgrind so awesome.

true, but you will have to use some macro-magic if you want to get rid of these memsets in case you want optimal runtime-performance. To avoid cluttering my code with #if HAVE_VALGRIND directives, I usually add a “myvalgrind.h” header in my project to always have the macros available.

Leave a comment!

«

»

Find it!

Theme Design by devolux.org

Tag Cloud