EmbeddedRelated.com
Forums

Delay Routine: Fully-portable C89 if possible

Started by Martin Wells October 9, 2007
John Devereux wrote:
> David Brown <david@westcontrol.removethisbit.com> writes: > >> John Devereux wrote: >>> Grant Edwards <grante@visi.com> writes: >>> >>>> On 2007-10-13, Martin Wells <warint@eircom.net> wrote: >>>>> David: >>>>> >>>>>> That is why the <stdint.h> types >>>>>> were defined, and that is why they are important if you want portable >>>>>> code that works on a wide range of targets (at least, for architectures >>>>>> that can support 8, 16 and 32-bit data - targets with 36 bit cpus, for >>>>>> example, will always be problematic for portable code). >>>>> For what purposes would you want an integer type of a given >>>>> size (other than an integer type of *at least* a given size)? >>>> When you want perform modulo-N arithmetic. >>> Hmm.. don't the low-order N bits of an unsigned always obey modulo-N >>> arithmetic? I.e, use the "guaranteed long enough" type then mask at >>> the point of use. >>> >>> For example you could do >>> >>> uint8_t index; >>> ... >>> index++; >>> buffer[index] = c; >>> >>> or >>> >>> unsigned char index; >>> ... >>> index++; >>> buffer[index&0xff] = c; >>> >>> The compiler should optimize this to the same code. Also this way I >>> can use other masks for different N. >>> >> The first example is correct - you are using a specific-sized integer >> type, which will wrap as expected. It is not entirely portable (cpus >> without 8-bit support will not have a uint8_t, and it's conceivable >> that a cpu could be built with 8-bit support but not modulo >> arithmetic). >> >> The second example uses a nonsensical type "unsigned char" (it may >> work like a uint8_t, but the name is silly), and requires an extra >> expression when index is used, thus complicating the code and adding >> scope for programmer errors. It is also not the case that the >> compiler *will* optimise to the same code - it perhaps *should* do so, >> but not every compiler is as good as it ought to be, and sometimes >> optimisations are disabled or limited (such as for some types of >> debugging, or examining generated assembly code). > > The thing is, this was a contrived example, like all the others I can > think of unfortunately. In *practice*, I would not code a circular > buffer like this (modulo-N arithmetic). Even if did, it is unlikely to > be exactly 256 bytes long, so I would have to use the explicitly > masked version anyway. >
Fair enough. I thought the point of Grant's post was that sometimes you would specifically want modulo-N arithmetic based on type sizes without needing extra bit masks. mvh., David
In message <47132be3$0$3193$8404b019@news.wineasy.se>, David Brown 
<david@westcontrol.removethisbit.com> writes
>Martin Wells wrote: >> Hans-Bernhard: >> >>> It would be rather nice if a VALUE_BITS like that were a C89 >>>standard >>> functionality, wouldn't it? Well, sorry, it's not. And not only that: >>> it can't even be implemented in C89. There's only CHAR_BIT (in >>> <limits.h>), and sizeof() --- but the latter doesn't work in >>> preprocessor instructions. >> #define VALUE_BITS(m) (((m)-1) /(((m)-1)%0x3fffffffL+1) >>/0x3fffffffL >> %0x3fffffffL *30 \ >> + ((m)-1)%0x3fffffffL /(((m)-1)%31+1)/31%31*5 + 4-12/(((m)-1)%31+3)) >> Works for all unsigned integer types. >> Martin >> > >How exactly is that monster macro supposed to be used, and what result >does it give?
Err..... "Highly Commended" in the Obfuscated C (Macro section) competition? :-) -- \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ \/\/\/\/\ Chris Hills Staffs England /\/\/\/\/ /\/\/ chris@phaedsys.org www.phaedsys.org \/\/\ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
John Devereux wrote:
> David Brown <david@westcontrol.removethisbit.com> writes: > >> Martin Wells wrote: >>> David: >>> >>>> That is why the <stdint.h> types >>>> were defined, and that is why they are important if you want portable >>>> code that works on a wide range of targets (at least, for architectures >>>> that can support 8, 16 and 32-bit data - targets with 36 bit cpus, for >>>> example, will always be problematic for portable code). >>> >>> For what purposes would you want an integer type of a given size >>> (other than an integer type of *at least* a given size)? The only >>> difference between a 33-Bit int and a 32-Bit int is that the former >>> will take longer to wrap around to zero upon overflow, and it will >>> also retain an extra bit when left-shifted. You'll still have all the >>> range you want though. >>> >>> Martin >>> >> There are several situations where a 33-bit integer would not suit >> when I want a 32-bit integer (or more realistically, reasons why I >> might particularly want a 16-bit integer and not a 32-bit integer). >> Here's a few of them: >> >> 1) RAM space - on a small micro, you don't want to waste space. > > I would use "short" for this. >
But a "short int" may be bigger than 16-bit. Some 8-bit compilers may may also implement "short int" as 8-bit - I know it's non-conforming, but it's far more likely that an embedded programmer will meet a non-standard compiler with 8-bit shorts than a standard compiler with 36-bit ints.
>> 2) Speed - on a small micro, larger integers take a lot more time and >> code space to work with. > > What I find is that I might write code for, say, an AVR and perhaps > use "short" to reduce the code space, run time and ram space. Then, I > might want to use the code in an ARM version. It runs just fine, since > I used portable code. Now it might be that if I used int_fast16_t, > then the code could run microscopically more efficiently on the > ARM. But it probably would make no difference. (And because the ARM is > so much faster generally, it would not be noticable). >
The speed difference will, of course, vary depending on the code in question, and the cpus in question - but some 32-bit cpus work twice as fast on 32-bit data as they do on smaller data, and 8-bit cpus clearly work much faster on the smaller data. As you say, one solution is to use int_fast16_t and similar types - you specify precisely the characteristics of the type that you are interested in.
>> 3) Interfaces with hardware - you want to get the sizes matching exactly. > > I am not usually worried about portability for this, since such code > is inherently portable. I can use my knowledge that e.g. unsigned > chars are 8 bits and unsigned longs are 32 bits in this case. >
Here you are implicitly using the size specifications of the types. I prefer to be explicit about such things.
>> 4) Interfaces with external equipment or other programs - if you are >> sending data to another program, you want to be 100% sure of the data >> formats. > > I find if I want to be portable, I have to manually pack/unpack data > anyway due to endianness issues. So the fixed length types don't buy > me anything. >
Sometimes endianness is an issue. I'd love C to have types with specified endianness as well as size.
>> 5) Bit manipulation - sometimes it's important to know the exact >> number of bits when doing logic operations. > > Maybe - but I can't think of a real-life example. >
extern void sendChar(char c); void sendHexNibble(uint8_t n) { static const char hexChars[] = "0123456789abcdef"; sendChar(hexChars[n]); } void sendHexByte(uint8_t n) { sendHexNibble(n >> 4); sendHexNibble(n & 0xf); } If the uint8_t used by sendHexByte had extra non-zero bits, you would end up reading outside the hexChars[] array.
>> 6) Array efficiency - it is often more efficient to access arrays of >> structs if the element size is a power of two for easy calculation of >> addresses, so it's important to know the exact size of the elements. > > You mean padding the struct with extra elements, or adjusting the type > of existing ones, to bring the total struct size to a power-of-two > number of bytes? I have never thought of doing this, but it sounds > very unportable. I suppose it could avoid doing a multiply on lookup. >
It's perfectly portable - as long as you use size-specific types. And yes, it avoids multiplies on lookups - multiplies can be costly on small cpus. Even on big cpus, avoiding multiplies can be beneficial - sometimes multiplies have longer latencies than shifts, and many have addressing modes that can include shifts in a single instruction. The other advantage is that you can be sure that accesses are aligned. The compiler should pad the struct to ensure that accesses are possible, but not necessarily optimal - if you know the exact sizes, you can add padding yourself to choose a balance between space and speed.
>> There are probably many more reasons, but that's enough to get started. > >
Martin Wells wrote:
> David: > >> When writing programs, it's important that they are understandable and >> make sense when read. Thus the type "char" makes sense for holding a >> character, or as part of a string - it does not make sense for holding a >> number, for example. The types "unsigned char" and "signed char" do not >> make any sense at all, and thus should only appear hidden away in a >> header - typedef'ed names such as "uint8_t" and "sint8_t" make vastly >> more sense. > > > With time you come to ignore the English-language meaning of the > keywords. When's the last time you thought about the meaning of > "static" when you wanted a private function, or a variable that stays > around for the next function call. >
That's a good point (although "static" as a word makes sense when applied to a local variable). But when there is a choice, I prefer to make sense - "unsigned int" makes more sense than plain "unsigned". If "static" functions called alternatively be written "private" functions, I'd use "private" for improved readability.
Martin Wells wrote:
> Ulf: > >> char's are sometimes set to unsigned char on small micros, >> and to be able to guarantee signedness regardless of >> compiler options is an improvement. > > > Plain char's should be used to store characters, so their sign is > irrelevant. If you want to use a byte for arithmetic, then try signed > char or unsigned char. > > Martin >
On this point we agree entirely! If you know whether your compiler's plain chars are signed or unsigned, you're probably doing something wrong. mvh., David
David Brown <david@westcontrol.removethisbit.com> writes:

> John Devereux wrote: >> David Brown <david@westcontrol.removethisbit.com> writes: >>
[...]
>>> There are several situations where a 33-bit integer would not suit >>> when I want a 32-bit integer (or more realistically, reasons why I >>> might particularly want a 16-bit integer and not a 32-bit integer). >>> Here's a few of them: >>> >>> 1) RAM space - on a small micro, you don't want to waste space. >> >> I would use "short" for this. >> > > But a "short int" may be bigger than 16-bit. Some 8-bit compilers may > may also implement "short int" as 8-bit - I know it's non-conforming, > but it's far more likely that an embedded programmer will meet a > non-standard compiler with 8-bit shorts than a standard compiler with > 36-bit ints.
If a "short" is bigger that 16 bit, surely the int_least16_t will be too? They both have the same rationale AFAIK. [...]
> >>> 5) Bit manipulation - sometimes it's important to know the exact >>> number of bits when doing logic operations. >> >> Maybe - but I can't think of a real-life example. >> > > extern void sendChar(char c); > void sendHexNibble(uint8_t n) { > static const char hexChars[] = "0123456789abcdef"; > sendChar(hexChars[n]); > } > void sendHexByte(uint8_t n) { > sendHexNibble(n >> 4); > sendHexNibble(n & 0xf); > } > > If the uint8_t used by sendHexByte had extra non-zero bits, you would > end up reading outside the hexChars[] array.
I confess, I would probably just go ahead with the exact same code but using unsigned char as a synonym for uint8_t. They are always the same on the machines I am familiar with. (And if they were not, there would probably not be a uint8_t at all!)
>>> 6) Array efficiency - it is often more efficient to access arrays of >>> structs if the element size is a power of two for easy calculation of >>> addresses, so it's important to know the exact size of the elements. >> >> You mean padding the struct with extra elements, or adjusting the type >> of existing ones, to bring the total struct size to a power-of-two >> number of bytes? I have never thought of doing this, but it sounds >> very unportable. I suppose it could avoid doing a multiply on lookup. >> > > It's perfectly portable - as long as you use size-specific types. And > yes, it avoids multiplies on lookups - multiplies can be costly on > small cpus. Even on big cpus, avoiding multiplies can be beneficial - > sometimes multiplies have longer latencies than shifts, and many have > addressing modes that can include shifts in a single instruction. The > other advantage is that you can be sure that accesses are aligned. > The compiler should pad the struct to ensure that accesses are > possible, but not necessarily optimal - if you know the exact sizes, > you can add padding yourself to choose a balance between space and > speed.
Aren't compilers free to pad structures how they like? [...] -- John Devereux
David:

> The very > existence of the typos (such as the "& 1" instead of "| 1" near the end) > shows how ugly and error-prone they are.
No, it just proves that I was smart to write "(Unchecked, likely to contain an error or two)". My checked-over code is a different animal altogether.
> Good code is clearly > understandable and easily readable, and does not contain unnecessary > code (either source code, or instructions generated which the compiler > must then optimise away).
Yes, good code is clearly understandable and easily readable *where possible*.
> Thus the correct way to get the lowest 32 bits of x, assuming x is not > known to be 32-bit or less, is simply (x & 0xffffffff).
Yes, that's a hard-coded way of doing it. Martin
John Devereux wrote:
> David Brown <david@westcontrol.removethisbit.com> writes: > >> John Devereux wrote: >>> David Brown <david@westcontrol.removethisbit.com> writes: >>> > > [...] > > >>>> There are several situations where a 33-bit integer would not suit >>>> when I want a 32-bit integer (or more realistically, reasons why I >>>> might particularly want a 16-bit integer and not a 32-bit integer). >>>> Here's a few of them: >>>> >>>> 1) RAM space - on a small micro, you don't want to waste space. >>> I would use "short" for this. >>> >> But a "short int" may be bigger than 16-bit. Some 8-bit compilers may >> may also implement "short int" as 8-bit - I know it's non-conforming, >> but it's far more likely that an embedded programmer will meet a >> non-standard compiler with 8-bit shorts than a standard compiler with >> 36-bit ints. > > If a "short" is bigger that 16 bit, surely the int_least16_t will be > too? They both have the same rationale AFAIK. >
Yes, I think that's true. But if the target/compiler does not support a true 16-bit int, then I the type "int16_t" would not exist (even though int_least16_t and int_fast16_t would). Note that it is perfectly possible, though somewhat unlikely in this contrived example, for an embedded compiler to support 16-bit integers while having short ints bigger than 16 bits. Embedded compilers sometimes support different sized types as extensions, beyond what is available through char, short int, int, long int and long long int. It's more realistic to expect sizes such as 24-bit or 40-bit integers to be available in this way, but some compilers have 32-bit short ints (perhaps having 64-bit ints, or perhaps just because 32-bit data are much faster on the architecture in question), and may provide 16-bit integers through an extension.
> > [...] > >>>> 5) Bit manipulation - sometimes it's important to know the exact >>>> number of bits when doing logic operations. >>> Maybe - but I can't think of a real-life example. >>> >> extern void sendChar(char c); >> void sendHexNibble(uint8_t n) { >> static const char hexChars[] = "0123456789abcdef"; >> sendChar(hexChars[n]); >> } >> void sendHexByte(uint8_t n) { >> sendHexNibble(n >> 4); >> sendHexNibble(n & 0xf); >> } >> >> If the uint8_t used by sendHexByte had extra non-zero bits, you would >> end up reading outside the hexChars[] array. > > I confess, I would probably just go ahead with the exact same code but > using unsigned char as a synonym for uint8_t. They are always the same > on the machines I am familiar with. (And if they were not, there would > probably not be a uint8_t at all!) >
I hope you don't have to try to get code working on one of these horrible 16-bit DSPs that can't access 8-bit data directly - an unsigned char is 16 bits on such architectures. I'd rather have the compiler reject the code because there is no uint8_t implemented, than generate incorrect code because I'd assumed a char is 8 bits.
>>>> 6) Array efficiency - it is often more efficient to access arrays of >>>> structs if the element size is a power of two for easy calculation of >>>> addresses, so it's important to know the exact size of the elements. >>> You mean padding the struct with extra elements, or adjusting the type >>> of existing ones, to bring the total struct size to a power-of-two >>> number of bytes? I have never thought of doing this, but it sounds >>> very unportable. I suppose it could avoid doing a multiply on lookup. >>> >> It's perfectly portable - as long as you use size-specific types. And >> yes, it avoids multiplies on lookups - multiplies can be costly on >> small cpus. Even on big cpus, avoiding multiplies can be beneficial - >> sometimes multiplies have longer latencies than shifts, and many have >> addressing modes that can include shifts in a single instruction. The >> other advantage is that you can be sure that accesses are aligned. >> The compiler should pad the struct to ensure that accesses are >> possible, but not necessarily optimal - if you know the exact sizes, >> you can add padding yourself to choose a balance between space and >> speed. > > Aren't compilers free to pad structures how they like? >
Yes, and they are allowed to generate all sorts of code as long as the visible results are the same. The padding will (normally!) follow specific rules according to alignment rules on the target, but it's certainly advisable to check that the padding is as you expect. I generally have the "-Wpadding" flag on my gcc compiles, so that I get a warning if my structs are unexpectedly padded.
David:

> You can contest all you want. At best, you can argue that it is > *possible* to write embedded code (portable or otherwise) without having > specific sized types, but there is no doubt that people write better > code by taking advantage of these types.
The C language is described in such a way that "int" should be the most efficient type (or at the very least tied for first place). If I want to store a number, I use unsigned. If the number can be negative, I use int. If the number needs more than 32-Bits, I use long. If I need to conserve memory, I use char if it has enough bits, otherwise short. If I *really* need to conserve memory, I make an array of raws bytes and do bit-shifting. There's nothing wrong with the likes of uint_atleast_8, it's just that they're not portable C89. I've heard there's a fairly efficient fully- portable C89 stdint.h header file going around, so maybe that would be useful.
> You are clearly new to > embedded development, at least on small micros (judging from your > original post in particular) - those of us who have been developing on a > wide range of cpus for years understand the benefits of size-specific > types. That's why <stdint.h> was introduced, that's why number 1 hit on > lists of shortcomings of C is its inconsistent type sizes, and that's > why embedded programmers always like to know the exact size of the int > types on their target. Sure, it's possible to get away without it - > just as its possible to do embedded programming without C - but why > *not* use size specific types to your advantage?
Again I don't see much use for them. If you want efficiency, go with int. If you wanna save memory, go with char if possible, otherwise short. If you've got big numbers, go with long.
> >> Certainly many of the situations where size specifics are important are > >> in hardware dependant and non-portable - and thus the only issue is that > >> the code in question is clear. > > > The microcontroller I'm using currently has 6-Bit ports but 8-Bit > > registers and memory chunks... still no problem though. > > You'll find that these are logically 8-bit registers, with only 6 bits > implemented.
Indeed the highest two bits are ignored when outputing to the pins. Martin
John:

> Now it might be that if I used int_fast16_t, > then the code could run microscopically more efficiently on the > ARM.
"int_fast16_t" shouldn't be anything other than plain old int. Martin