C
Chris Croughton
As far as why determining the endianness may be important, I often
write code to communicate among devices via SPI on devices where the
transmit buffer is 8 bits. Since I have to send data as a stream of
bytes, endianness becomes important. All the existing code assumes big
endian micros. We only use 2's complement implementations but I don't
see why we might not switch to a little-endian architecture.
The portable way to do it is to decide in your protocol which way round
things are going to be passed ("network byte order" as used in TCP/IP is
defined by the standards for that protocol as MSB first, for instance)
and convert the data using C arithmetic operators:
void put8bit(unsigned char v);
void put16(unsigned short v)
{
put8(v >> 8);
put8(v);
}
void put32(unsigned long v)
{
put16(v >> 16);
put16(v);
}
unsigned char get8(void);
unsigned short get16(void)
{
unsigned short v = (unsigned short)get8() << 8;
return v + get8();
}
unsigned long get32(void)
{
unsigned long v = (unsigned long)get16() << 8;
return v + get16();
}
I've used unsigned short and unsigned long because the former is
guaranteed to be at least 16 bits and the latter guaranteed to be at
least 32 bits. If you have stdint.h on your sustem you can use the
appropriate sized types.
The functions are illustrative, and I've deliberately left the 8 bit put
and get functions undefined, additional parameters can be added, there's
no error checking, etc. The point is that the routies are totally
agnostic of the underlying representations on the target machine and are
thus fully portable.
putX() and getX() can easily be defined for structures in that way.
If you want to pass signed data, you will need to build into the
protocol a portable method of doing it, since not all processors are 2-s
complement (although in practice you may be able to assume that for your
system). Something like:
void puts32(long v)
{
if (v < 0)
{
/* possibly handle LONG_MIN == 0x80000000 case */
put32(0x80000000 | -v);
}
else
put32(v);
}
long gets32(void)
{
unsigned long v = get32();
if (v & 0x80000000)
return -(long)(v & 0x7FFFFFFF);
else
return (long)v;
}
Or something like that.
In regard to your point about embedded systems in general needing to
know what endianness they are, in almost all cases[1] this can be
determined by the system builder before compilation and set a define to
flag which is being used:
#ifdef MSB_FIRST
/* do little-endian stuff */
#else
/* do big-endian stuff */
#endif
Anything like that should be restricted to a small part of the program,
so that if you need to cope with something like the PDP-11
bastard-endian (3412 or something like that) you can easily add extra
code to do it.
An alternative way of doing it is to provide macros or functions,
contitionally compiled to get the right ones, which do the conversions.
Look at the POSIX ntohl and ntohs macros, for example.
Chris C