EmbeddedRelated.com
Forums
The 2024 Embedded Online Conference

Delay Routine: Fully-portable C89 if possible

Started by Martin Wells October 9, 2007
In article <47122090$0$3197$8404b019@news.wineasy.se>, David Brown 
says...
> 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.
That, presumably, would be why your applicaqtion specific integer types would often boil down to unsigned char or signed char.
> 2) Speed - on a small micro, larger integers take a lot more time and > code space to work with.
That's why int exists. It's minimum size requirements cause an issue with 8-bit micros in that regard, but otherwise it will be the native integer type (and thus the most efficent). Any non 8bit platform for which that's not true? In particular any 16 bit platform that uses a 32 bit int? It's forbidden by what I uderstand to be the spirit of the language. The int(size) definitions, however, select a type w/o regard for its efficiency. The fast and at least types do provide some advantages here, but I don't think I've ever seen them 'in the wild'. The int(size) macro are butt barnacles on the language to coin a phrase. In any case none of the stdint type should IMO be used directly, but rather as the basis of application specific types where appropriate. The application specific types then give you additional type checking (beyond size).
> 3) Interfaces with hardware - you want to get the sizes matching exactly.
At that point you are so specific that using the appropriate underlying type is no more difficult. There is a minor documentation advantage but that's it. On a number of interfaces even that is misleading. There are inferfaces where a 32 bit number must be broken into parts for writing and others where an 8 bit number must be written as a 32 bit so the size of the I/O and the size of the value written (or read) may be somewhat independant.
> 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.
But size only covers a small portion of that issue. You also have to deal with endianess and alignment at least. Size is actually the least troublesome IME.
> 5) Bit manipulation - sometimes it's important to know the exact number > of bits when doing logic operations.
Well if you are doing logic it's one bit by definition ;) Bitwise operations are another question but width is only an issue when going to the hardware. Theres some modulo arithmetic that's used for checksums etc but that's a small portion of the code I've dealt with. Modulo arthmetic on buffers is best done in other ways. Now a standard way to introduce MAC operations and saturating arithmetic might be interesting but I doubt there's enough experience with them to justify burden the language with them.
> 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.
This one rather left me open mouthed with astonishment. Robert -- Posted via a free Usenet account from http://www.teranews.com
David:

> 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.
Use fully-portable code to pick the most appropriately sized type provided by the implementation.
> 2) Speed - on a small micro, larger integers take a lot more time and > code space to work with.
Again, fully portable
> 3) Interfaces with hardware - you want to get the sizes matching exactly.
I dunno how important this is. I'm currently working with a microcontroller that has 6-Bit ports but which has 8-Bit registers.
> 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.
Fair enough, it would be a hell of a lot more efficient if you were using an implementation that provided exact types you're looking for, but it's still not impossible to do it portably.
> 5) Bit manipulation - sometimes it's important to know the exact number > of bits when doing logic operations.
Again simple to do with portable code.
> 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.
Not quite sure how this applies here. Martin
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). So while the second example might be more useful for those writing code that must compile cleanly on an AVR and a PDP-6, back in the real world of embedded development, the first version is greatly preferred.
Martin Wells wrote:
> Hans-Bernhard Br&#4294967295;ker: > >>> 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. >> I'm sure you'll be prepared to show the world how to mask modulo 2^32 >> arithmetic, given that pre-C99 standard C doesn't offer any way to >> express 2^32 as a numeric constant? > > > It's easy to get only the bits you want: > > (Unchecked, likely to contain an error or two) > > #define BITMASK_X_BITS_SET(one_expression,x) ( \ > ( \ > ( \ > ( \ > (one_expression) << (x-1) \ > ) \ > \ > -1 \ > ) \ > \ > << 1 \ > ) \ > \ > & 1 ) > > #define GET_LOWER_X_BITS(val,x) ((val) & > BITMASK_X_BITS_SET((val)*0+1,x) > > Martin >
These macros of yours just roll of the tongue, don't they? The very existence of the typos (such as the "& 1" instead of "| 1" near the end) shows how ugly and error-prone they are. 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). 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). And if you need 2^32 as a constant (which you don't for 32-bit modulo addition and subtraction), the most practical way is to write 0x100000000ull, or perhaps (1ull << 32), since any realistic embedded development compiler that supports more than 32-bit integers will support long long ints.
Martin Wells wrote:
> David: > >> 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. > > > Use fully-portable code to pick the most appropriately sized type > provided by the implementation. >
I don't know what you mean by this. If I want the smallest type that holds 16-bit signed integers, I use a sint16_t - that's portable, and gives me exactly what I want.
> >> 2) Speed - on a small micro, larger integers take a lot more time and >> code space to work with. > > > Again, fully portable >
And again, I don't know what you mean here. Getting the smallest and fastest code is *not* fully portable, because the types are different. If I want a datatype that can hold at least 8 bits, but is as fast as possible, it should be 8-bit on an AVR and 32-bit on a ColdFire. There is no way I'd want my AVR code cluttered up with junk to improve the speed on a ColdFire - I want my code to be readable, and would rather write two clear and optimal versions than one illegible and sub-optimal "portable" version. In a case like this, if portability is of real concern, then using the <stdint.h> "sint_fast8_t" would be the answer.
> >> 3) Interfaces with hardware - you want to get the sizes matching exactly. > > > I dunno how important this is. I'm currently working with a > microcontroller that has 6-Bit ports but which has 8-Bit registers. >
Your 6-bit ports are probably accessible as 8-bit hardware registers. But trust me, getting the access size on hardware registers right is often (though not always) important.
> >> 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. > > > Fair enough, it would be a hell of a lot more efficient if you were > using an implementation that provided exact types you're looking for, > but it's still not impossible to do it portably. >
But why on earth would you want to jump through hoops to get this mythical "portability" instead of just using size-specific types in the first place?
> >> 5) Bit manipulation - sometimes it's important to know the exact number >> of bits when doing logic operations. > > > Again simple to do with portable code. >
But clearer, simpler, faster to write, faster to debug, and perhaps smaller and faster to execute if you know the size of the types you are using.
> >> 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. > > > Not quite sure how this applies here. >
Look at the code: typedef struct { int x; short int sx; } data_s; data_s array[200]; int foo(int i) { return array[i].x; } If "int" is 32 bits, and "short int" is 16 bits, the structure is 6 bytes long. This has two effects - the "foo" function has to multiply its index by 6, and the int elements will be misaligned. On many cpus, a multiply by 8 is faster than a multiply by 6, and on a cpu with a 32-bit bus, an aligned 32-bit access is normally significantly faster than an unaligned access. Thus the array could be faster (though larger) with a 16-bit padding element in the struct. Since I use types of known size in such declarations, I can make that sort of decision.
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. [...] -- John Devereux
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.
> 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).
> 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.
> 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.
> 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.
> 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.
> There are probably many more reasons, but that's enough to get started.
-- John Devereux
Martin Wells wrote:
> David: > >> *All* C standards are implemented to varying degrees, and *all* embedded >> compilers add their own extensions. Take advantage of what you get >> (such as <stdint.h>, inline, and // comments), and leave out the parts >> that are poorly or inconsistently implemented (such as variable length-co >> arrays). Even nominally C89 compilers frequently support such features. > > > There's nothing at all wrong with extra features, so long as they > don't alter the behaviour of fully-standard-compliant programs or > prevent them from compiling successfully. >
I agree that extra features should not cause problems with standard code. Just remember that these features were added for a reason - if they let you write better, or smaller, or faster, or clearer code, then use them if they provide more benefit that portability would (that's one reason I like gcc - I can use the same extra features on many targets).
> >> First the simple part - omitting the "int" part of declarations and >> definitions is an abomination brought about by the terrible keyboards >> K&R had to work with when developing C. The type in question is called >> "long unsigned int" - "long" and "unsigned" are type qualifiers. The >> language standards say that if a type is expected, but you haven't given >> one, then an "int" is assumed, and any C compiler will accept that. But >> the compiler will accept all sorts of hideous code and generate working >> results - you have to impose a certain quality of code standards if you >> are going to write good code. One of these rules is that if you mean a >> type should be "int" (optionally qualified), then you write "int". If >> you don't want to write out "long unsigned int", then use a typedef. > > > I do indeed write my code to a quality standard, and that standard > finds nothing wrong with "unsigned". The majority of good C > programmers leave out the redundant keywords unless they really wanna > be explicit about what they're doing. When was the last time you saw > "auto" in a C file? Or even in the real world, who writes a plus sign > behind positive numbers? >
You write the redundant information if it is useful. Sometimes (though not often) it is useful to include the plus sign on positive numbers. Some people think it is useful to use "auto". I am very much of the opinion that writing "int" in all cases is important, even if it is technically redundant. The English you write here is full of redundant words and letters that are not necessary to communicate your message. Why not try writing in SMS-speak, which has much less redundancy, and see what people here think about your post's legibility? Redundancy is a *good* thing, when it adds to what you write, but a bad thing if it merely wastes space and distracts from the important parts of the code.
> >> Secondly, I was suggesting that if you want portable code, you have to >> use size-specific integer types. > > > Nope definitely not, you just have to be guaranteed to have a given > range with the type. For example: > > short unsigned : at least through 65535 > long : at least -2147483647 through 2147483647 > > >> Using <stdint.h> is an easy way to get >> that - otherwise, a common format header file that is adapted for the >> compiler/target in question is a useful method. It doesn't really >> matter whether you use "uint32_t" from <stdint.h>, or have a "typedef >> unsigned long int uint32_t" in a common header file - nor does it matter >> if you give the type your own name. But it *does* matter that you have >> such types available in your code. > > > Again I contest the need for this. >
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. 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?
> >> 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.
David Brown <david@westcontrol.removethisbit.com> writes:

> 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.
Well, perhaps that was a "straw man" argument - I am having trouble thinking of real examples that would actually *use* "modulo-N arithmetic based on type sizes". -- John Devereux
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?

The 2024 Embedded Online Conference