Re: My idea of fully-portable C code
- From: Dave <dhschetz@xxxxxxxxx>
- Date: Tue, 13 May 2008 12:02:38 -0700 (PDT)
On May 12, 6:10 pm, Tomás Ó hÉilidhe <t...@xxxxxxxxxxx> wrote:
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 <stdint.h>
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 <string.h> /* memset */
#include <limits.h> /* CHAR_BIT */
#include <assert.h>
#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 << (x)) - 1u)
#define CHUNK_ALL_ONES (BITS_ALL_ONES(BITS_PER_CHUNK))
static char unsigned data[BYTES_NEEDED]; /* Raw memory for storing
chunks */
typedef struct ChunkAddress {
char unsigned *pbyte;
uint_fast8_t i_bit;
} ChunkAddress;
static ChunkAddress GetChunkAddress(uint_fast16_t const chunk)
{
# define i_bit_in_entire_data (chunk * BITS_PER_CHUNK)
# define i_byte (i_bit_in_entire_data / CHAR_BIT)
ChunkAddress addrs;
assert(chunk < QUANTITY_CHUNKS);
addrs.pbyte = data + i_byte;
addrs.i_bit = i_bit_in_entire_data % CHAR_BIT;
return addrs;
# undef i_bit_in_entire_data
# undef i_byte
}
uint_fast8_t GetChunk(uint_fast16_t const i)
{
ChunkAddress const addrs = GetChunkAddress(i);
char unsigned x = *addrs.pbyte;
assert(i < QUANTITY_CHUNKS);
x >>= 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 <<= bits_in_current_byte;
# undef bits_in_current_byte
# undef bits_in_next_byte
return x|y;
}
# endif
return x & CHUNK_ALL_ONES;
}
void SetChunk(uint_fast16_t const chunk, uint_fast8_t const cs)
{
ChunkAddress const addrs = GetChunkAddress(chunk);
char unsigned x = *addrs.pbyte;
/* ---- First clear the chunk bits ---- */
x &= ~(CHUNK_ALL_ONES << addrs.i_bit);
/* Now OR the actual chunk shifted to the correct place */
x |= (unsigned)cs << addrs.i_bit;
*addrs.pbyte = (char unsigned)x; /* Truncation warning cast */
# 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];
/* ---- First clear the chunk bits ---- */
y &= ~BITS_ALL_ONES(bits_in_next_byte);
/* ---- Now OR with chunk bits ---- */
y |= cs >> bits_in_current_byte;
# undef bits_in_current_byte
# undef bits_in_next_byte
addrs.pbyte[1] = (char unsigned)y; /* Cast to suppress
truncation warning */
}
# endif
}
void SetEntireDataAllOnes(void) { memset(data,-1,sizeof data); }
void SetEntireDataAllZeroes(void) { memset(data,0,sizeof data); }
/* ------------------------ End Source File: data_acc.c
------------------------------ */
Of course, at the end of all that, you'll want to test it to see if it
actually works, so here's a sample tester:
/* ------------------------ Begin Source File: main.c
------------------------------ */
#include <stdio.h>
#include <stdint.h>
#include "data_acc.h"
uint_fast8_t fat_array[42];
int main(void)
{
uint_fast16_t i;
uint_fast8_t chunk_value = 0;
puts("Filling the fat array...\n");
for (i = 0; 42 != i; ++i)
{
fat_array[i] = chunk_value++;
chunk_value %= 4;
}
puts("Filling the skinny array...\n");
for (i = 0; 42 != i; ++i)
{
SetChunk(i,fat_array[i]);
}
puts("Comparing the two...\n");
for (i = 0; 42 != i; ++i)
{
if (GetChunk(i) == fat_array[i])
printf("Equal. Value = %u\n",(unsigned)fat_array[i]);
else
puts("ERROR");
}
return 0;}
/* ------------------------ End Source File: main.c
------------------------------ */
So that's it.
I've written this post to show an example of fully-portable code, that
is, code that will behave exactly as intended on every conceivable
implementation of the C89 standard.
Of course, people will give examples of more complicated algorithms,
things like encryption, and I will admit that these things are more
complex to implement portably, but not impossible.
In C, the "unsigned char" type has no padding bits and thus it allows
you full access to a contiguous piece of memory, and therefore it can
be used to do whatever the you want with memory. At times, this can be
the key to implementing fully-portable code.
If you're working with a machine such as an Intel PC and you're
writing an encryption algorithm, then it probably is more efficient to
make assumptions about the size of a byte, about the lack of padding
in integer types -- and these non-portable assumptions will probably
lead to more efficient assembler, but with that said, portable
programming in C is not impossible.
So far, I've had a few people contest my claim of writing fully-
portable C code. Most of these people also took the opportunity to
attack me personally instead of focusing on the discussion. I did not
write this thread for those people; I wrote this thread in the hope of
enlightening the friendly, polite people about fully-portable C
programming, and also to rest any doubts they had about me not being
capable of it.
And lastly, but certainly not least, I'm not infallible. I've checked
over the above code a few times, but I haven't actually sat down and
gone thru it with a fine-tooth comb. If there's any errors or
oversights, please feel free to (politely) point them out to me.
Let's hope this doesn't start a flame.
I don't think I get it. It is my understanding that this is supposed
to be portable microcontroller code. Alright, what are printf and puts
doing in main? Who said there was a screen to print to? That won't
work on my embedded AVR app.
In fact, where does your code have any interface with the outside
world? Where do you read or set the current status of the LED's you
talk about? Wouldn't this require reading and writing a register,
which is inherently specific to a platform? Do you want to use a UART
to send those messages in the printf and puts? Maybe there are two to
choose from. It seems to me that as soon as you want to interface to
the outside world in an embedded app, you need to do something that is
device-specific. Doing things internally, with no external visibility
and no way to talk to the rest of the system, and saying its portable
is not very useful.
Am I missing something?
Dave
.
- References:
- My idea of fully-portable C code
- From: Tomás Ó hÉilidhe
- My idea of fully-portable C code
- Prev by Date: Re: My idea of fully-portable C code
- Next by Date: Re: PMOS in parallel with NMOS
- Previous by thread: Re: My idea of fully-portable C code
- Next by thread: HaRET 0.5.1 / HP iPaq hw6915
- Index(es):
Relevant Pages
|