6

I'm debating the merits (bikeshedding?) of two different approaches to a simple problem.

This setting is numeric, but may not exist, so the options are:

  • Does not exist
  • 0
  • 1
  • 2
  • 3

The programming language in use is C, so there are two obvious ways of encoding this into a byte. Option 1 is to use values 0, 1, 2, 3 and 0xff to indicate that the value is undefined. Option 2 is to use bit0 to specify whether the value is defined and bits 1 and 2 to define the value (when bit0 is 1).

My personal feeling is that option 2 is superior because in-band signalling is a likely source of errors. Though I do concede that the shifting/masking might make option 2 a bit slower.

Which option would you prefer and why? Or suggest another option.

  • 1
    Option 1 is practically a form of option 2. With option 1, you're using bits 2 through 7 to specify whether the value is defined, and bits 0 and 1 to define the value (when bits 2 through 7 are 0). The only "advantage" I see for option 2 is that you have to explicitly convert the value whenever you see it. – Tanner Swett Oct 21 '16 at 4:36
6

I'd prefer the first option because it can be easily represented as an enum with well-defined names, and would make the code more readable. The second option could also be made readable, with a wrapper function (e.g. convertIntegerToOptionEnum), but that feels like an unnecessary complication.

The one situation where I'd go for option 2 is where we have very tight memory/network optimizations, and are doing structs with bitfields (http://www.catb.org/esr/structure-packing/#_bitfields), leaving the remaining 5 bits for other useful information. Such situations are few and far between though.

However, I'm missing your in-band signalling concern: maybe describing the overall use case would tip the balance in that direction?

P.S. I've learned the term "bikeshedding" - thanks!

1

It's not clear from your question, but it sounds like the function returns char - is this correct?

More importantly, are the values 0, 1, 2, and 3 to be interpreted as numbers, or are they just representations of another semantic domain (enumerations)? This is crucial to making the right choice. If they are just meant to represent four distinct choices, then you should be using an enum.

If not, then I think either choice is equally valid. Both are a form of in-band signaling, by the way. In C that's common, and there's not much alternative. There is a "trick" you could do to emulate an option type:

struct option {
    char c;
    bool is_valid;
}

This isn't much in the spirit of C, though.

  • I have updated the question to point out that the values are numeric and not another domain with a number assigned to it (such as colors) – Dave Oct 21 '16 at 4:28
0

A common approach is to use a (signed) integer. Anything that is negative is 'undefined'. This has an advantage over using 0xFF as a sentinel value since it is technically a valid positive number if you are using an unsigned char. You can define your sentinel as -1, or check that your value is > 0.

#include <stdint.h>
#define UNDEFINED -1
uint8_t i = UNDEFINED;
...
i = some_value;
if (i>=0) { do something interesting } else { undefined }

From a defensive programming perspective you still have a problem. You always need to check that the variable is >=0 before you use it. You could get a gnarly bug by using a negative value in a computation.

The other solution of using a struct to hold a 'valid' bit has the same problem. To have guaranteed safety you should always access the data struct via an API to avoid accidentally referencing the data value when the flag says it is invalid.

typedef struct {
  unsigned char valid:1;
  unsigned char value:7;
} myType;
myType i ={0,0}; // initialize to invalid
...
i.valid=1;
i.value=some_value;
...
if (i.valid) {do something interesting } else { undefined }

You can see the above has the same potential to access i.value when it is not valid. So you would need to define an API to do any work with this data to keep this from happening. For example:

myType add_numbers(myType first, myType second)
{
myType returnValue = {0,0};
if (first.valid && second.valid) 
  { returnValue.valid = TRUE;
    returnValue.value = first.value + second.value;
    // TODO deal with overflow here
  }
  return (returnValue);
}

You may want to avoid the overhead of creating a complex API. There is one other solution with works well with the language and will prevent any unintentional data references. It allows you to truly have the concept of an undefined value in your code. This solution is to use pointers. A pointer will either point to a valid data element or it will be undefined, which is exactly what you are looking for. The risk of course is that your code will crash if it attempts to access an undefined value unless you always check (so you can't get away from that).

unsigned char * theData = NULL;
...
// data gets a value
unsigned char actualData = something;
theData = &actualData;
...
if (!theData) { do something interesting } else { undefined }

I suggest the choice should be governed by your tolerance for error and how you can protect yourself from bugs. The API approach is the safest if you expect others to use this code. As long as the API is followed then bugs are minimized. The other two approaches are open to bugs that result in either the wrong value being computed, or the program crashing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.