Portability is always within some range of platforms. C is not a perfectly portable language; it is possible to build machines on which you cannot implement C (or on which you cannot implement C reasonably well). So the question of portability is always one of what range of platforms you support.
By using C, you support some subset of platforms that have working C compilers. Obviously. But what you do with C can narrow that support.
If your code depends on the sizeof(T)
of an integral type, in order to keep it portable you should use the types in stdint.h
like uint8_t
or uint32_t
If you write code that depends on the size of a type, you are not writing code that is as portable as C itself. As you have been informed, uint8_t
and such types are not required by the C standard. By any C standard; they're optional features of C99 and C11. And C++11 and above, in case you were wondering.
But portable "as far as the standard is concerned" is not necessarily the end-all of portability. There are after all a lot of platforms which can and do support those types. Your code can be portable within those platforms.
What you are being warned about really is not so much reliance on the sized types. What you're being warned about is reliance on type size. It is that which limits your platforms, because C doesn't define type sizes. C allows the individual platforms to do so.
Therefore, code which relies on having a fast, 32-bit integer type is by its very nature not portable to platforms that don't have such things. As an example of this, Vulkan bills itself as a cross platform graphics API. And it is. But Vulkan defines all of its types in terms of the stdint.h
sized integer types. Why?
Because if a platform can't support them, then the platform cannot support Vulkan period. So there's no point in pretending that Vulkan can run on anything that C can.
That's not wrong; it's simply setting your limitations based on the needs of your code. You are frequently sending data structures directly to and from Vulkan, and you need to be able to match what implementations expect/require. So your C platform needs to support types that can match these.
At the same time, if you're not doing something that explicitly needs a specific size of integer, then you're doing yourself a disservice by using an explicitly sized integer type when an int
would do. The int_leastXX_t
types are required, and they can handle most problems regarding integer ranges.
In what other way can I convince this guy that if you use the basic types like int
or short
, your code will not be portable?
Using these types will only be non-portable if you do non-portable things with them.
For example, bit manipulation is well-defined in C. The specific number of bits in int
is not. So the following code:
unsigned int i = 1 << 19;
Is perfect well-defined... so long as sizeof(unsigned int)
is 20 bits or more on that platform. So any platform where this is true will be fine.
By contrast:
uint32_t i = 1 << 19;
Is only well-defined code if the platform supports uint32_t
. If it has an unusual byte size (9 and 18 bits are not unknown byte sizes), then it cannot support uint32_t
. And therefore that code cannot work. But it may have worked for the first case, because int
might have been 36 bits in size.
By restricting yourself to the specific sized integer types, you are also restricting yourself to platforms that support those sized integer types. If you use the fundamental integer types, you are restricted to platforms where those types are "big enough" for whatever you use them with.
So which one is the more portable? It's hard to argue that the sized one is the more portable, when the unsized one can run on anything (which supports C) that has integers that are big enough. Whereas the sized one is restricted only to platforms that can support 32-bit integers. Even though that code isn't actually using 32 bits.
I suppose one advantage of the latter is that, if sized types are not supported, you get a hard compile error. By contrast, if int
is not big enough, you get invisible implementation-defined behavior. C++11 got a solution to that problem with static_assert
, which could prevent compilation on an arbitrary compile-time thing. Like the size of an int
being big enough for a particular use.
Every API uses a number of flags ored together. Flags are bit manipulation.
, another dubious statement. Technically speaking, ALL operations are bit manipulations.