The Artima Developer Community
The C++ Source | C++ Community News | Discuss | Print | Email | First Page | Previous | Next
Sponsored Link

The C++ Source
Built-in Type Safety?
by Thomas Guest
June 25, 2005

Page 1 of 4  >>

Advertisement

Summary
C++ is a statically typed language but its type system is not bulletproof. This article reveals some all-too-common type glitches and how to fix them.

Introduction

C++ is statically typed. Programmers inform their compiler about the types they are using. In return the compiler warns them of any type abuse it detects. Static typing doesn't cure all known defects, of course. On occasion, a defect which appears type-related can slip through the type system undetected. This article tells the true story of two such defects. It discusses how they might have been avoided and concludes with some more general advice.

Defect One

Signal Quality

The following function lives somewhere in the middleware of the Talk-To-Me wearable computer (see Sidebar below). The Talk-To-Me, when active, polls a hardware driver for signal quality information. This driver returns a Signal, which is a simple typedef for a built-in integral type. Some variation in the signal is to be expected, depending on location, atmospheric conditions and so on; but if a more substantial change occurs then the new value must be stored in RAM and written to flash memory. That's what this function does.

The Talk-To-Me Communicator
The Talk-To-Me communicator is a wearable computer. Despite its limited size and processing power it runs a range of software, including an email client, a personal organiser, and a cut-down web browser. It is an imaginary device used here to fictionalise the defects described in this article, which occurred, in real life, on a similar platform.
    /**
     * Handle a signal update.
     * If a change in the signal is detected, store the new value
     * and write it to flash. 
     */
    void
    signalUpdate(Signal update, Signal & stored)
    {
        int const tolerance = 10;
        
        bool const changed 
            = update > stored + tolerance
              ||
              update < stored - tolerance;
        
        if (changed)
        {
            flashWriteSignal(update);
            stored = update;
        }
    }
Maybe this function isn't perfect—perhaps the detection of change and subsequent action should be subfunctions, and certainly the numeric literal requires explanation—but it looks sound enough. In fact, it is so apparently sensible that the Talk-To-Me engineers are surprised when field tests reveal problems.

Problems

Writing to flash is expensive. It takes time, and, eventually, wears out the hardware. Field tests show that in poor signal conditions the Talk-To-Me is writing to flash far too often. The signalUpdate() function is to blame.

Attempted Solutions

Flash is only written to when the signal deviates by a certain amount from the previous stored value. Evidently the driver layer is reporting greater fluctuation than expected. The obvious solution is to bump up the magic number.
    int const tolerance = 20; // Used to be 10, but this caused
                              // too many calls to flashWriteSignal()
The changes are made, a patch is issued, field tests resume. Unfortunately, the problem persists—if anything, it seems worse.

There is only one way out:

    if (changed)
    {
        // flashWriteSignal(update); Avoid excessive writes to flash
        //                           Works around driver problems.
        stored = update;
    }
This rather drastic step means that the Talk-To-Me will lose settings when power-cycled, which isn't ideal, but at least a show-stopping defect can be downgraded to medium priority. Flaky hardware drivers get the blame. Field tests continue. The signalUpdate() function is left in this rather messy state.

Unit Tests

Once the panic has passed, we can refactor the function in order to test it:
    void
    signalUpdate(Signal update, Signal & stored)
    {
        int const tolerance = 20; // Used to be 10
        
        if (signalsDifferent(update, stored, tolerance))
        {
            // flashWriteSignal(update); Avoid excessive writes to flash
            stored = update;
        }
    }
In a new file, then, we have:
    ....

    /**
     * @return True if the input signals differ by more than the supplied
     * tolerance, false otherwise.
     */
    bool
    signalsDifferent(Signal s1, Signal s2, int tol)
    {
        return 
            s1 > s2 + tol || s1 < s2 - tol;
    }
The associated unit tests read:
    void testSignalsDifferentBoundaryCase()
    {
        assert(!signalsDifferent(0, 0, 0));
    }
    
    void testSignalsDifferentTypicalSignal()
    {
        assert(!signalsDifferent(70, 80, 10));
        assert(!signalsDifferent(80, 70, 10));
    }
    
    void testSignalsDifferentPoorSignal()
    {
        assert(!signalsDifferent(10, 10, 20));
    }

Test Failure

The testSignalsDifferentPoorSignal() test case fails. At last the real problem is evident.

For the purposes of this article I have been withholding a critical detail: namely which built-in integral type a Signal is. Inspecting the relevant header file shows that:

    /**
     * Typedef used to represent signal quality as a percentage.
     * 100 represents perfect signal, 0 no signal.
     */
    typedef unsigned Signal;
So, the expression in signalsDifferent(10, 10, 20) evaluates: 10u > 10u + 20 || 10u < 10u - 20 Now, when you subtract an int from an unsigned both values are promoted to unsigned and the result is unsigned. So, 10u - 20 is a very big unsigned number. Our expression is therefore equivalent to:
    false || true
which is of course true.

In fact, signalsDifferent() returns true whenever the second input Signal dips below the tolerance. So, if the signal quality drops below 20%, flash memory is written to every time the driver is polled. Hence the bug reported during field tests.

History

Rewinding the header file shows that once upon a time we had:
    /**
     * Typedef used to represent signal quality as a percentage.
     * 100 represents perfect signal, 0 no signal.
     */
    typedef int Signal;
The code worked just fine back then. Unfortunately the C++ type system offered no help when someone decided that an unsigned was more appropriate for a value in the range [0, 100]. No compiler warnings were, in this case, bad news.

Perhaps it would be more accurate to say that the C type system failed to help since this is one of those areas where C++ finds itself compromised and constrained by its C heritage. The integral promotion rules are well and truly entrenched in the common core of the two languages.

Page 1 of 4  >>

The C++ Source | C++ Community News | Discuss | Print | Email | First Page | Previous | Next

Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us