I've been talking about portable C programming lately, and I've claimed that I write fully-portable code. Many people here contested this claim, some more rudely than others, so instead of trash-talking back and forth I'm going to actually give an example of the way I code.
In my most recent embedded project, there was a severe memory restriction. I had to store in memory the state of 42 individual LED's. Each LED could have one of three states. To use the least amount of memory possible for each LED, I thought I'd go with the following strategy:
- Use three bits per LED. 00 = Off. 01 = Red. 10 = Unused. 11 = Green.
I decided to go about writing a "module", a C source and header file couple that would handle this tight memory access and provide a nice, clean, easy-to-use interface. The module would need two pieces of information from the programmer:
- The amount of combinations (i.e. 3 for my LEDs' states) * The amount of chunks (i.e. 42 because I've 42 individual LED's)
From there, it would handle all the data accessing, both reading and writing. All the programmer has to do is invoke "GetChunk" and "SetChunk".
In starting out to write this module, I knew I wanted it to be as portable as possible. I wanted it to work on machines that have an 8- Bit byte. Also those funky old machines that have a 9-Bit byte. Also supercomputers that have a 64-Bit byte.
First off, I started with a header file to provide an interface. I refer to the little 3-Bit chunks of memory as a "chunk", and so the functions are "SetChunk" and "GetChunk". (Of course you can set the chunk size bigger or smaller than 3 bits).
Here's the header file:
/* ------------------------ Begin Header File: data_acc.h
------------------------------ */ #ifndef H__DATA_ACC #define H__DATA_ACC
#include
void SetChunk(uint_fast16_t const i, uint_fast8_t const state); uint_fast8_t GetChunk(uint_fast16_t const i);
void SetEntireDataAllZeroes(void); void SetEntireDataAllOnes(void);
#endif /* ------------------------ End Header File: data_acc.h
------------------------------ */
Before I show the source code, I'm going to mention a complication. Let's take a canonical system such as an Intel PC that has an 8-Bit byte. If we were to use 3 bits per LED, then we'd be able to store 2 LED's in one byte and we'd have 2 bits left over (i.e. 2(3) + 2 = 8). Well, instead of discarding those bits, what my code does is it takes the last 2 bits from the previous byte in conjunction with the 1st bit of the next byte. No bits are wasted on any system, regardless of the amount of bits in a byte or the amount of bits per "chunk".
Now before I show the source file, I have to tell you about the two constants that are required from the programmer:
QUANTITY_CHUNKS (e.g. This is 42 for the 42 LED's) BITS_PER_CHUNK (e.g. This is 2 because I've 2 bits per LED, e.g. 01 = Red)
These two constants shall be defined in a file called "data_acc_specs.h". Here's a sample:
#define QUANTITY_CHUNKS 42u #define BITS_PER_CHUNK 3u
How the source code works is as follows. Internally, it has the concept of a "chunk pointer", i.e. a data type that gives it all the information it needs to locate a specific chunk in memory. The chunk pointer consists of two things: * The address of the byte in which the first bit resides * The index of the bit in that byte
When you invoke GetChunk for instance, it will convert the index of a chunk to a "chunk pointer", and this chunk pointer will give it the information it needs to locate and read the relevant chunk.
Here's the source code:
/* ------------------------ Begin Source File: data_acc.c
------------------------------ */ #include "data_acc.h" #include "data_acc_specs.h"
#include /* memset */ #include /* CHAR_BIT */ #include
#define TOTAL_BITS_NEEDED (QUANTITY_CHUNKS * BITS_PER_CHUNK)
#define BYTES_NEEDED (TOTAL_BITS_NEEDED / CHAR_BIT + !! (TOTAL_BITS_NEEDED % CHAR_BIT))
#define BITS_ALL_ONES(x) ((1u >= addrs.i_bit;
# if CHAR_BIT % BITS_PER_CHUNK || !defined(NDEBUG) /* Chunk can spill into next byte */
if (addrs.i_bit + BITS_PER_CHUNK > CHAR_BIT) /* Has spilt into next byte */ { # define bits_in_current_byte (CHAR_BIT - addrs.i_bit) # define bits_in_next_byte (BITS_PER_CHUNK - bits_in_current_byte)
char unsigned y = addrs.pbyte[1];
y &= BITS_ALL_ONES(bits_in_next_byte);
y