views:

508

answers:

9

I'm working on a project on an 8051 where every byte counts. As such, I am using some global variables where I normally wouldn't. The normal method of passing pointers into a function adds too much overhead here.

I have a number of functions that use single bit variables (a compiler specific extension to C) to signal the outcome of a function in addition to the normal return value.

bit global_error_flag = 0;
bit global_data_ready_flag = 0;

unsigned char A_Function (void) {
    // Do some stuff

    if ( badness ) {
        global_error_flag = 0;
        global_data_ready_flag = 1;

        return 0;
    }

    if ( data_is_ready_use ) {
        global_data_ready_flag = 1;
    }

    return a_value;    
}

void Other_Function (void) {
    unsigned char c;

    c = A_Function();

    if( global_error_flag) {
        // Do error stuff here.
    }
    else
    if( global_data_ready_flag ) {
        // Do data processing here.
    }
    global_error_flag = 0;
    global_data_ready_flag = 0;

}

Given that the technique is evil, is there some way I can make the code clearer?

How best should I indicate which function calls will have the side-effect of setting these flags? Are comments enough? Should I name the functions to indicate their API (quasi-hungarian-style)? Should I use a macro to mark such calls:

#define FUNCTION_SETS_FLAGS(code) (code)

FUNCTION_SETS_FLAGS( c = A_Function() );

Any other ideas?

+6  A: 

Using a convention, whether you want to call it "Hungarian" or not, is the best way I can think to mark this offhand. Stylistically, some sort of naming prefix would be preferable over the empty #define, at least to me.

This is actually pretty common, I think. I know that the S60 programming environment uses a lot of conventional tags on functions to indicate that they throw exceptions, for example.

Dan Olson
+3  A: 

Your globals are labelled for clarity, that's a good start.

Ideally you want something that won't compile if you get it wrong. That means macros and comments won't work.

I'd stick to a naming convention for the functions - not necessarily Hungarian, but something like A_Function_Returns_Flags, or less verbose if you can think of that.

Mark Ransom
+1  A: 

This doesn't really help you, but GCC has a way to do the opposite of what you want: to mark functions which have no side effects. See the const and pure attributes. This is more for optimization than documentation, thought: if the compiler knows that a given function does not examine any data other than its arguments, it can perform smarter optimizations such as loop-invariant code motion.

Adam Rosenfield
A: 

First I would try to code it in a way that there are only one producer and only one consumer for each of those flags. Then I would clear/set a flag only when needed. As for indicating the side-effect, A standard header on top of the function, doxygen style, should be enough:

    // Function func
    // Does something
    // Consumes ready_flag and  sets error_flag on error.

    int func()
    {
        if (ready_flag)
        {
            //do something then clear the flag
            if (some_error)
                error_flag = x;
            ready_flag = 0;
        }
        //don't mess with the flags outside of their 'scope'
        return 0;
    }

On the other hand, if the error and ready flags are mutually exclusive, you could use a byte (or bits inside a byte/register) to indicate readiness or an error state.

0 for an error, 1 for not-ready/error-free and 2 for ready/error-free (or -1, 0, 1, whatever)

IIRC, the standard 8051 instruction set doesn't operate on single bits, so using a whole byte for (various) flags shouldn't give you a huge performance hit.

Marcelo MD
The 8051 has range of memory locations that are bit addressable. Internal memory bytes 20-2F mapped to bit addressable space. Bits are settable with setb and clr; while jb, jbc, and jnb do logical tests. So, "setb 00h" is the same as "orl 20h, #0x01", except more efficient.
daotoad
Heh, Live and learn.When I used the 8051 I had the absurd amount of 64kB ROM and 2kB of RAM. With that, I didn't bother looking for that much efficiency (the project used 59kB/1.5kB max) =D
Marcelo MD
Yeah, I like that kind of project better, too. That way I can write for readability and sanity, and not have to carefully weigh every bit and byte used.
daotoad
+3  A: 

I did my Ph.D. on a similar issue in Java. I can tell you the one thing you shouldn't do: don't rely on the documentation because then you depend on someone actually reading it. You need to add some hint in the method name to indicate that the user should read the docs to learn about side effects. If you pick something and are consistent with it, you probably stand the most chance.

Uri
+3  A: 

If you just want to mention that a function affects global variable(s), then a simple (Hungarian) prefix might help.

But if you want to mention every single flag(s) that it affects, then, using the function header is probably the way to go. Like for example,

  /*************************************************************************
     * FUNCTION    : <function_name>
     * DESCRIPTION : <function description> 
     * PARAMETERS  : 
     *  Param1 - <Parameter-1 explanation>
     *  Param2 - <Parameter-2 explanation>
     *  Param3 - <Parameter-3 explanation>
     * RETURN      : <Return value and type>
     * GLOBAL VARIABLES USED: 
     *  Global1 - <Global-1 explanation>
     *  Global2 - <Global-2 explanation>
     *  Global3 - <Global-3 explanation> 
  *************************************************************************/
Alphaneo
+1  A: 
quinmars
A: 

If you really have to stick with these global variables, you can make it obvious that a function may modify them by expecting references to them as function arguments:

unsigned char A_Function (bit *p_error_flag, bit *p_data_ready_flag)
{
  ...
}
geschema
I am working on a system with 8 8 bit registers, and only 256 bytes of memory. The 8 registers use up 8 of the 256 bytes of memory. Using pointers consumes resources that are not available. Even passing unused pointers around is wasteful. Otherwise I would use pointers.
daotoad
A: 

If you haven't done so already, you might also want to check out the sdcc project on sourceforge, it's a C compiler specifically meant to be used for embedded development which also targets the 8051, in addition the compiler supports a number of custom, target-specific and non-standard compiler intrinsics for various use cases, also I have personally found the development team to be very open to and responsive about ideas for new enhancements and other related feature requests.

none