Reputation: 51
It has come to my attention that there seems no consistency nor best practise when to use native scalar types (integer, short, char) or the ones provided by stdint: uint32_t uint16_t uint8_t.
This is bugging me a lot because drivers form an essential part of a kernel that needs to be maintainable, consistent, stable and good.
Here is an illustrational example in gcc (used this for a hobby project for the raspberry pi):
// using native scalars
struct fbinfo {
unsigned width, height;
unsigned vwidth, vheight;
unsigned pitch, bits;
int x, y;
void *ptr;
unsigned size;
} __attribute__((aligned(16)));
// using stdint scalars
struct fbinfo {
uint32_t width, height;
uint32_t vwidth, vheight;
uint32_t pitch, bits;
int32_t x, y;
uint32_t ptr; // convert to void* in order to use it
uint32_t size;
} __attribute__((aligned(16)));
To me, the first example seems more logical, because this piece of code is only intended to run on a raspberry pi. It would be pointless to run this on other hardware.
The second example seems more practical, because it looks more descriptive since C does not guarantee much about the size of integers. It may be 16 bits or something else. uint32_t, uint_fast32_t and variants make guarantees about the exact or approximated size: e.g. at least or at most X bytes.
The operating system development community tends to use the stdint types, while the linux kernel uses multiple different techniques: u32, __u32, and endian specific stuff like __le32.
What considerations should be taken into account when to choose a scalar type and when to use a typedef'd scalar type? Is it better to use native scalar types in the provided example or use stdint.h's?
Upvotes: 4
Views: 2175
Reputation: 6984
Fixed width types are sometimes difficultly to use. E.g. printf()
specifiers for int32_t
are PRIi32
and require splitting of the format string:
printk("foo=" PRIi32 ", bar=" PRIi32 "\n", foo, bar);
Fixed width types should/must be used when hardware is accessed directly; e.g. when writing DMA descriptors. But for simple register accesses, writel()
or readl()
functions can be used which work with fundamental types.
As a rule of thumb, when a certain memory layout is assumed (like the __attribute__((__aligned__(16)))
in your example, fixed width types should be used.
Signed fixed width types (int32_t x,y
in your example) might need double checking, whether their representation matches the hardware expectations.
NOTE that in your example, the second structure is architecture dependent because of
uint32_t ptr; // convert to void* in order to use it
Writing such thing in common C would be uintptr_t ptr
and in the kernel it is common to write
unsigned long ptr;
Alternatively, dma_addr_t
might be a better type.
uint32_t
vs. __u32
More than 10 years ago, Linus Torvalds objected against uint32_t
because at this time, non-C99 compilers were common and using such types in (exported) linux headers would pollute the namespace.
But now, uint32_t
and similar types are available everywhere (you can not compile the kernel with a non-C99 compiler) and kernel header export has been improved significantly, so these arguments are gone.
It is a matter of personal preference whether to use standard types or typedef'ed variants (which are framework dependent and differ between them).
uint_fastX_t
and variantsThey are not used in the kernel and I would avoid them. They combine disadvantages of uint32_t
(difficult usage) and int
(variable width).
__le32
vs. __u32
Use the endian types when specification explicitly requires them (e.g. in network protocol implementations). This makes it easy to detect wrong usage (e.g. assignments like endian_variable = native_variable
).
Do not use them e.g. for filling processor structures (e.g. DMA descriptors); some processors can run both in little and big endian mode and native datatypes are usually the right way to write such infomration.
Upvotes: 5