5

Forgive me if this comes across as a trivial question - I'm usually a control systems guy (plc's and automation) but have found myself involved in some embedded micro-controller and PC projects lately.

Let's say I have a function that accepts a pointer to an array of 'command bytes', typically 5 or 10 bytes in length, like so:

char cmd_i2c_read(unsigned char *cmd, unsigned short cmd_len) { ... }

I want to decode the command bytes (*cmd).

Is it better form to:

  1. Create local variables indicating the purpose of each byte:

    unsigned char device_address = cmd[2];
    unsigned char register_address = cmd[3];
    unsigned char num_bytes = cmd[4];
    // use the local variables: if(num_bytes &le 0xFF) { do_stuff(device_address, register_address, num_bytes); }
  2. Create local pointers:

    unsigned char *device_address = &cmd[2];
    unsigned char *register_address = &cmd[3];
    unsigned char *num_bytes = &cmd[4];
    // use the pointers: if(*num_bytes &le 0xFF) { do_stuff(*device_address, *register_address, *num_bytes); }
  3. Index the *cmd array directly:
    if(cmd[4] <= 0xFF) { do_stuff(cmd[2], cmd[3], cmd[4]); }

2
  • See what I mean by preference :) Commented Feb 23, 2010 at 20:54
  • And then we can start arguing about if the if statement should be on a single line or if 0xFF should be made into a constant. :-) Commented Feb 23, 2010 at 21:10

7 Answers 7

4

Option 1 is clear but a bit wordy. I don't like 2 at all, and 3 is hard to understand. Personally I prefer to use structs for this sort of thing.

typedef struct  {
   unsigned char whatever[2];
   unsigned char device_address;
   unsigned char register_address;
   unsigned char num_bytes;
   }  CMD;

CMD * pcmd = (CMD *)&cmd[0];

// use the local variables:
if(num_bytes ≤ 0xFF) {
    do_stuff(pcmd->device_address, pcmd->register_address, pcmd->num_bytes);
Sign up to request clarification or add additional context in comments.

7 Comments

Doesn't this assume that CMD can be arbitrarily-aligned?
Obligatory struct padding warning: This, of course, should be done with care. The struct is not guaranteed to have its elements contiguous in memory, so the struct may not match exactly the layout of the input data. If your compiler has a something like gcc's __attribute__((packed)), it would at least be a good idea to use that. This (as it is) may not even port bw differing versions of the same compiler.
@Alok: as long as the members of CMD are byte sized, there are no alignment issues.
Using a struct overlay like this avoids the data duplication of #1, the extra variables of #1 and #2, and the ambiguity of #3. As an added benefit, it reinforces the idea that these values are related to each other.
@John: I guess my question was if that is true according to the standard.
|
3

I prefer item 3 but it comes down to preference.

Comments

3

IMHO, the first way is better. It is much easier to read than number 3, because you don't need to know the signature of the function to understand what the parameters are.

For bigger data structures, I would go with number 2, i.e. use pointer such that you don't have to copy the values. But in this case, the difference is not significant, and I think that the */& slightly reduce the readability.

7 Comments

+1 I agree it's a lot easier to read device_address than cmd[2].
#3 would be the easiest to read if instead of using numbers, there were predefined constants for the indexes.
Why would 2 do better than 1 at not losing space? In 1, three unsigned chars are created, and in 2 three unsigned char *s are created.
@Kevin - but that is only because you know cmd[2] is a device address, so what is the difference? I could of easily just assigned it to a temp variable called device_address to fulfill the wish...My point is the programmer knows that cmd[2] or cmd[index] represents a specific value for a specific type. Otherwise he / she wouldn't know how to call do_stuff() //in this case...
@David: I edited my answer ;) ! I was referring to bigger data structures, where a pointer is really smaller than the whole data. But in this case, it does not change much !
|
2

Definitely #1. It makes the rest of the code much more readable, and using a pointer where a normal variable will do complicates the matter for no reason. The compiler will optimize away whatever microscopic performance gains you might get from #3.

2 Comments

with a dumb compiler, #3 would be the slowest in most cases.
You could also make the local variables const - you should find that with optimisation enabled, the compiler will optimise those variables out of existence.
1

If it were me I'd write this as #3 but with symbolic names for the array indices to improve readability:-

#define DEVICE_ADDRESS 2
#define REGISTER_ADDRESS 3
#define NUM_BYTES 4

if (cmd[NUM_BYTES] <= 0xFF) {
    do_stuff(cmd[DEVICE_ADDRESS], cmd[REGISTER_ADDRESS], cmd[NUM_BYTES]);
}

You can of course replace the macros with const ints, enums, etc. The reason why I like this option is that the other two options require the use of additional local variables. The compiler may or may not optimize these away depending on its implementation and your chosen level of optimization but they just seem like an unnecessary level of extra indirection to me.

Comments

0

I like 1 the most, it's much more readable than the others.

If performance is an important issue (i mean important like in "we need every 0.01% speedup we can get"), you will have to benchmark cause it really depends on the compiler which sequence will end up in the fastest code (1 may have unnecessary copys, 2 may contain superflous loads due to pointer aliasing restrictions, 3 may lead to superflous loads if the register allocator is really messed up)

Comments

0

To me it depends on What You'll Be Doing with your buffer,

All things being equal though, I'd probably prefer to have the buffer be a struct (assume alignment warings) and, failing that, to directly index the buffer, although not with magic numbers.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.