• Please review our updated Terms and Rules here

C code portability question

alank2

Veteran Member
Joined
Aug 3, 2016
Messages
2,265
Location
USA
Some devices have 8 bit registers, some 16 bit, some 32 bit, etc. Sometimes I write code that I want to be used on different platforms with a different number of bits.

I see two approaches to this, but I wonder which is better:

#1 - Use an "int" and let it be 32-bit on a 32-bit platform and 16-bit on a 16-bit platform. It would be wasteful on a 8-bit platform if an uint8_t could do the job.

#2 - Use whatever will hold the data, for example a small loop from 0-23 could use a uint8_t to do it. Does this create unnecessary code for 16-bit or 32-bit plaforms that have larger registers? Do they have to put in extra instructions to deal with the smaller rollover and use of the uint8_t?
 
You can do what I do--use the <stdint.h> definitions, rather than the implied types. e.g. uint8_t is an 8 bit unsigned quantity; int32_t is a signed 32-bit quantity.
If you don't like typing the explicit types out, you can typedef them to something you do like; e.g. BYTE, WORD...etc.
This greatly enhances portability. I've taken code verbatim from a Linux gcc program and moved it to an MCU with virtually no changes.

As far as what's faster or slower, is very difficult to say. A 32 bit integer on some platforms can be faster than a 16-bit one. A platform that depends on certain alignments in structs is an example, where members "straddle" boundaries in a packed struct.

If your C is too old to include stdint.h in its library, you can easily add it.
 
I prefer having the size of the variable be defined for all cases. May waste some space or be less than fully efficient on hardware using larger ints but prevents any surprises from embedding assumptions as to the size of the int.
 
If you are using a variable as a loop counter (or something similar) I would use an 'int' on the basis that this will have been chosen by the compiler writer to be the most efficient on the architecture you are running.

There are macros that I use ( e.g. sizeof( int ) ) to ensure (at runtime) that this meets my minimum requirements for loop counters etc.

If you are making something portable - I would use explicit typing to ensure that what I want is what I get...

The use of "fractions of an int" (e.g. uint8_t) are somewhat dependent upon the native instruction set. If the machine has access to byte instructions, then little overhead should be incurred. If not, well...

Dave
 
Chuck - that approach is exactly what I've been doing. One annoyance is that any math/formulas automatically escalate to int anyway and then when you assign them to a smaller variable you get the warning about it. I can cast around the warning, but it is annoying.

krebizfan - that has been my thinking too.

>If you are using a variable as a loop counter (or something similar) I would use an 'int' on the basis that this will have been chosen by the compiler writer to be the most efficient on the architecture you are running.

This is my one gripe against using explicit sizes, you aren't really using the register size of the system and I wonder if it is less than ideal for performance where it might have not mattered to use "int" for multiple platforms. Maybe it is less of a concern because the larger you go in bits, the more CPU power you'll have anyway, but I just wonder if using a uint8_t or int16_t as a loop counter forces a 32 bit system to have to and it with 0x0000ffff or something.

I'm trying to find the "best" way between both methods I guess, but maybe each has its pros/cons.
 
GNU C, for example, has a built in way to dealing with much of this with its "least" and "fast" integer declarations. So you can do something like int_least16_t to ensure you get at least a 16 bit representation or the "fastest" 16 bit representation. Sometimes this will force the compiler to align or pack the data differently rather than use a different size. Thats not an issue for structures populated at runtime. Where it gets dicey is when you are persisting / serializing some data structure where you want the on-disk structure to be consistent across all platforms. In those cases size definite types are needed.

-- Bob
 
A postscript for coders: If you're dealing with an external structure (say a table on disk, directory structure, etc.) never, ever assume the size or generic types (e.g. int, long, double) or their packing in a structure. (I have code in my libraries to handle 6-bit characters, for example)
 
I did a test making a loop with uint8_t, uint16_t, and uint32_t. All had 6 instructions in the loop section, so maybe it isn't a huge concern and going with the explicit is the way.
 
Again, depends on the platform. Using uint32_t on a 16 bit system, I would imagine, uses more instructions, as there isn't a 32-bit add in the 16 bit repertoire.
 
This is my one gripe against using explicit sizes, you aren't really using the register size of the system and I wonder if it is less than ideal for performance where it might have not mattered to use "int" for multiple platforms. Maybe it is less of a concern because the larger you go in bits, the more CPU power you'll have anyway, but I just wonder if using a uint8_t or int16_t as a loop counter forces a 32 bit system to have to and it with 0x0000ffff or something.
Do you have particular platforms in mind?

You remind me to mention that for x86_64, the 32-bit and 8-bit operations on a register have the most compact encoding. A 64-bit or 16-bit operation requires a prefix byte. One of the rules introduced with x86_64 is that if you touch the bottom 32-bits of a 64-bit register using a 32-bit or smaller instruction, the top 32-bits are automatically zeroed. So the compiler can often use the 32-bit instruction if it knows that the 64-bit result will always have a zero top half.
 
Bringing the topic back to the issue raised by the title, I'd have to say that the most portable code will use the explicit length typing.

Sometimes you can't have speed and portability in the same programming model.
 
size_t is unsigned, at least 16 bits, and big enough to hold a value equal to the maximum value of an array index.

It might be worth looking into as a loop counter, especially if you're using that counter to index into an array.

The difficulty comes, I guess, from the fact it is only guaranteed to be 16+ bits in width. If you write code on a 32 or 64 bit machine and assume it will translate to something smaller then you could run into issues.

But perhaps this is simply a use case for assert()s or compile time checking of sizes. You don't have to do it everywhere you use the type, but perhaps include a header file somewhere with a bunch of portability sanity checks in it?
 
How does that figure? I routinely program with byte arrays (buffers) larger than 128KB. 16 bits won't do that.
I believe what the poster is referring to is that the C standard has two overlapping requirements for size_t. One is that size_t is the type that the sizeof operator and offsetof macro yields, so it's going to be some "natural" size of the machine since you might apply the sizeof operator to a such a 128K array. The other is that regardless of the machine, size_t must be at least 16 bits wide. Due to the former requirement, the only kind of situations where a 128K array is possible yet size_t is only 16 bits would be those like 8086/286 huge pointers. Are there others relevant to your code?
 
I don't think so--on most 32-bit MCUs, an int is 32 bits unless otherwise noted. The x86 huge model is a mess, although I suspect a _far pointer also works.
 
GNU C, for example, has a built in way to dealing with much of this with its "least" and "fast" integer declarations. So you can do something like int_least16_t to ensure you get at least a 16 bit representation or the "fastest" 16 bit representation. Sometimes this will force the compiler to align or pack the data differently rather than use a different size. Thats not an issue for structures populated at runtime. Where it gets dicey is when you are persisting / serializing some data structure where you want the on-disk structure to be consistent across all platforms. In those cases size definite types are needed.

-- Bob

Yeah, just use int_fast16_t and let the compiler figure it out.
 
>>> In those cases size definite types are needed.

Of course, you also need endian-consistent types as well for portability...

Dave
 
Back
Top