Below is a function that implements a 128-bit counter in C. It expects an array containing 16 bytes (totaling 128 bits) and increments its value by one. The byte at index 0 is the low-order byte, thus the counter is in fact a little-endian counter. Note how any carry is elegantly propagated to higher-order bytes by checking for wrap-around of the currently incremented byte.
1 2 3 4 5 6 7 8 9 10 |
static void incrementCounter(uint8_t counter[16]) { int i; for (i = 0; i < sizeof(counter); ++i) { if (++counter[i] != 0) { break; } } } |
This code looks neat, compiles fine and works — but only partially. Depending on your platform, it might only use the lower 4 or 8 bytes of the 16 bytes that you pass to the function. How come?
Even though counter looks like an array of 16 bytes, it is actually a pointer. In C, looks can certainly be deceiving!
As a consequence, on a 32-bit platform sizeof(counter) yields 4 (8 on a 64-bit platform, respectively). According to the language rules of C/C++, arrays are always passed by reference (by passing a pointer to the first array element) and there is nothing we can do about it.
Don’t get fooled into believing that dereferencing the counter pointer fixes the problem:
1 2 3 |
for (i = 0; i < sizeof(*counter); ++i) { |
The type of counter is ‘pointer to uint8_t’, the dimension [16] is completely ignored by the compiler. Even worse: providing a dimension lures people into a false sense of type-safety. Hence my advice: avoid using array parameters in C (and C++).
Obviously, the unlucky programmer wanted to prevent people from making mistakes by explicitly showing that an array of 16 bytes is required. The irony is that in the end he screwed up himself.
What our programmer wanted to achive was this:
1 2 3 4 5 6 7 8 9 10 |
static void incrementCounter(uint8_t (*counter)[16]) { int i; for (i = 0; i < sizeof(*counter); ++i) { if (++(*counter)[i] != 0) { break; } } } |
Here, counter is declared to be a pointer to an array of 16 bytes and everything works as expected. But isn’t this somewhat ugly?
As almost always, a typedef can clean up things a bit:
1 2 3 4 5 6 7 8 9 10 11 12 |
typedef uint8_t counter_t[16]; static void incrementCounter(counter_t* counter) { int i; for (i = 0; i < sizeof(*counter); ++i) { if (++(*counter)[i] != 0) { break; } } } |
But my preferred solution is to wrap the array in a struct:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
typedef struct { uint8_t value[16]; } counter_t; static void incrementCounter(counter_t* counter) { int i; for (i = 0; i < sizeof(counter->value); ++i) { if (++counter->value[i] != 0) { break; } } } |
For most people, array members of structs are easier to grok than pointers to arrays. That’s why I try to avoid them whenever possible. Further, having a struct opens the door for future extensions and has the additional benefit of making direct value assignments possible without having to resort to memcpy:
1 2 3 4 5 |
counter_t counter1, counter2; ... counter2 = counter1; |