112

In today's cross-platform C++ (or C) world we have:

Data model  | short |   int |   long | long long | pointers/size_t  | Sample operating systems
... 
LLP64/IL32P64   16      32      32     64           64                Microsoft Windows (x86-64 and IA-64)
LP64/I32LP64    16      32      64     64           64                Most Unix and Unix-like systems, e.g. Solaris, Linux, BSD, and OS X; z/OS
...

What this means today, is that for any "common" (signed) integer, int will suffice and can possibly still be used as the default integer type when writing C++ application code. It will also - for current practical purposes - have a consistent size across platforms.

Iff a use case requires at least 64 bits, we can today use long long, though possibly using one of the bitness-specifying types or the __int64type might make more sense.

This leaves longin the middle, and we're considering outright banning the use of long from our application code.

Would this make sense, or is there a case for using long in modern C++ (or C) code that has to run cross platform? (platform being desktop, mobile devices, but not things like microcontrollers, DSPs etc.)


Possibly interesting background links:

Martin Ba
  • 7,578
  • 7
  • 34
  • 56
  • 15
    How will you deal with calls to libraries that use long? – Ángel May 05 '16 at 22:15
  • 15
    `long` is the only way to guarantee 32 bits. `int` can be 16 bits so for some applications it's not enough. Yes, `int` is sometimes 16 bits on modern compilers. Yes, people do write software on microcontrollers. I'd argue more people write software that has more users on microcontrollers than on PC with the rise of iPhone and Android devices not to mention the rise of Arduinos etc. – slebetman May 06 '16 at 03:00
  • 57
    Why not ban char, short, int, long, and long long, and use the [u]intXX_t types? – user253751 May 06 '16 at 04:16
  • 1
    Note that "Sample operating systems" doens't make sense, as nothing here is related to operating systems. The two rows are _compiler_ specific. GCC and Clang on Windows use LP64/I32LP64. – Mooing Duck May 06 '16 at 04:54
  • 2
    @slebetman long is no real guarantee for getting at least 32 bits, it only guarantees to have at least as much storage as `int`. If you want a guarantee, you need to look at the `int32_t` type. – Tommy Andersen May 06 '16 at 06:32
  • 3
    @TommyA: Actually, long guarantees 32 bits. If int is 32 bits then long is the same storage size as int but if int is 16 bits long MUST NOT BE 16 bits. See the spec. Both C and C++ requires long to be able to handle at least -2147483647 to 2147483647. While both C and C++ requires int to be able to handle at least -32767 to 32767. – slebetman May 06 '16 at 06:39
  • 1
    @slebetman I have consulted the specification, perhaps I am looking in the wrong place (§ 3.9.1.2) perhaps you could clarify which section you refer to? – Tommy Andersen May 06 '16 at 07:23
  • 2
    @TommyA: The C and C++ standards both require `long` to be able to store any value in the range `[-2147483647 .. +2147483647]`. That range requires 32 bits of storage. – Bart van Ingen Schenau May 06 '16 at 07:38
  • Looking through the latest draft of the c++ standard: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf I found no reference to this, which section are you referring to? – Tommy Andersen May 06 '16 at 07:40
  • 2
    @TommyA: It appears you are right. C++11 and above no longer specify the sizes of short int, int or long int. The only specified sizes is for long long int which must be at least 64 bits and char which must be at least 8 bits (via the weird language of requiring a mapping of 0-255 to values in unsigned char). C++14 don't even have mention of the minimum size of long long int. – slebetman May 06 '16 at 07:58
  • 7
    @slebetman I dug a bit deeper, it appears the requirement is still in place, although hidden in §3.9.1.3 where the C++ standard states: "The signed and unsigned integer types shall satisfy the constraints given in the C standard, section 5.2.4.2.1." And in the C standard §5.2.4.2.1 it states the minimum range, exactly as you wrote. You were absolutely right. :) Apparently owning a copy of the C++ standard, is not enough, one needs to find the copy of the C standard as well. – Tommy Andersen May 06 '16 at 08:02
  • @slebetman - Could you provide references to "16 bits on modern compilers" for desktop/moile platforms? (Otherwise you comment *is* good, noting that `long` does guarantee ~ 2^32 range while `int`does not.) – Martin Ba May 06 '16 at 08:16
  • 3
    @MartinBa: For neither pc nor mobile but for microcontrollers - Microchip XC16. I'm not as familiar with gcc targeting microcontrollers such as AVR (Arduino) but certainly XC16. It's a C compiler though, not C++. Micocontrollers are not what they used to be when I started programming. The hardware is still the same but the hobbyist/hacker community have embraced them in recent years with the introduction of easy to use platforms like Arduinos. – slebetman May 06 '16 at 08:24
  • @MooingDuck a compiler vendor can choose to ignore the conventions of the platform but by doing so they end up with a situation where every platform header will have to be adapted for their tools. According to https://cygwin.com/cygwin-ug-net/programming.html cygwin compilers use LP64 but mingw compilers use LLP64, this makes sense as cygwin is basically a platform in it's own right while mingw32 is more of a port of the tools for windos. – Peter Green May 06 '16 at 11:06
  • 13
    You're missing the DOSBox/Turbo C++ world in which `int` is still very much 16 bits. I hate to say it, but if you're going to write about "today's cross-platform world", you can't ignore the entire Indian subcontinent. – Lightness Races in Orbit May 06 '16 at 13:16
  • 4
    @LightnessRacesinOrbit The entire Indian subcontinent writes code in Turbo C++ targeting DOSBox? ***Why?*** – user253751 May 07 '16 at 10:22
  • 1
    @immibis: I don't know :( – Lightness Races in Orbit May 07 '16 at 12:56
  • @user253751 I think LRiO was making a comparison. If you want to make something universal, you can’t ignore a population of over a billion people. – Cole Tobin Mar 11 '20 at 15:41
  • @ColeJohnson A billion people write code in Turbo C++ targeting DOSBox? – user253751 Mar 11 '20 at 15:50

4 Answers4

207

As you mention in your question, modern software is all about interoperating between platforms and systems on the internet. The C and C++ standards give ranges for integer type sizes, not specific sizes (in contrast with languages like Java and C#).

To ensure that your software compiled on different platforms works with the same data the same way and to ensure that other software can interact with your software using the same sizes, you should be using fixed-size integers.

Enter <cstdint> which provides exactly that and is a standard header that all compiler and standard library platforms are required to provide. Note: this header was only required as of C++11, but many older library implementations provided it anyway.

Want a 64 bit unsigned integer? Use uint64_t. Signed 32 bit integer? Use int32_t. While the types in the header are optional, modern platforms should support all of the types defined in that header.

Sometimes a specific bit width is needed, for example, in a data structure used for communicating with other systems. Other times it is not. For less strict situations, <cstdint> provides types that are a minimum width.

There are least variants: int_leastXX_t will be an integer type of minimum XX bits. It will use the smallest type that provides XX bits, but the type is allowed to be larger than the specified number of bits. In practice, these are typically the same as the types described above that give exact number of bits.

There are also fast variants: int_fastXX_t is at least XX bits, but should use a type that performs fast on a particular platform. The definition of "fast" in this context is unspecified. However, in practice, this typically means that a type smaller than a CPU's register size may alias to a type of the CPU's register size. For example, Visual C++ 2015's header specifies that int_fast16_t is a 32 bit integer because 32 bit arithmetic is overall faster on x86 than 16 bit arithmetic.

This is all important because you should be able to use types that can hold the results of calculations your program performs regardless of platform. If a program produces correct results on one platform but incorrect results on another due to differences in integer overflow, that is bad. By using the standard integer types, you guarantee that the results on different platforms will be the same with regards to the size of integers used (of course there could be other differences between platforms besides integer width).

So yes, long should be banned from modern C++ code. So should int, short, and long long.

  • 23
    I wish I had five other accounts to up-vote this some more. – Gort the Robot May 05 '16 at 23:06
  • I suppose that is C++ specific? – Wildcard May 05 '16 at 23:54
  • 4
    +1, I've dealt with some strange memory errors that only happen when a struct's size depends on what computer you're compiling on. – Joshua Snider May 06 '16 at 00:06
  • 9
    @Wildcard it is a C header that is also part of C++: see the "c" prefix on it. There is also some way to put the typedefs in the `std` namespace when `#include`d in a C++ compilation unit, but the documentation I linked doesn't mention it and Visual Studio seems not to care how I access them. –  May 06 '16 at 00:07
  • Thank you! Fixed width integers should be the default int types imho. There is something to be said for thinking about and knowing your platform, but there is more to be said about sane representations of basic types across platforms. – WeRelic May 06 '16 at 04:13
  • 11
    Banning `int` may be ... excessive? (I would consider it if the code needs to be extremely portable across all obscure (and not so obscure) platforms. Banning it for "app code" may not sit very well with our devs. – Martin Ba May 06 '16 at 08:27
  • 5
    @Snowman `#include ` is _required_ to put the types in `std::` and (regrettably) _optionally allowed_ also to put them in the global namespace. `#include ` is exactly the converse. The same applies to any other pair of C headers. See: http://stackoverflow.com/a/13643019/2757035 I wish the Standard had required each only to affect its respective required namespace - rather than seemingly buckling to poor conventions established by some implementations - but oh well, here we are. – underscore_d May 06 '16 at 09:04
  • also, lol at the last line. well, hey, as long as you leave my `unsigned char` alone, we're fine. ;-) – underscore_d May 06 '16 at 09:07
  • One can also roll own `typedef`s, including specifically tailored ones for interaction with specific libraries. Whatever it takes to never write `int` again. – Agent_L May 06 '16 at 09:23
  • @MartinBa `int` is what started this folly of surprise-sized variables. `int` is the first type you learn to never write. – Agent_L May 06 '16 at 09:41
  • 1
    +1 So much this. If you need to guarantee type length across platforms, use `stdint.h` (if you're using c) or cstdint (for c++). – CHendrix May 06 '16 at 10:58
  • This is answer is kind of incomplete to support "*long should be banned from modern C++*" point. `intN_t` types are not enough to replace `int`-family, their purpose is different. The best equivalent of `int`-like types are `int_fastN_t`, or `least`-family. – luk32 May 06 '16 at 11:34
  • 1
    I agree with the notion that non-size specific integers should not be used, *except* for int when used for small values, where the underlying type should be chosen according to hardware capabilities and not fixed to a particular size (as some platforms may be more efficient with 16-bit values and some with 32). – Jules May 06 '16 at 13:51
  • 1
    Many, many development shops have zero interest in making sure their software is cross-platform. Most software is written for internal use only, and so it only needs to run in a well-defined environment. 10 years from now, it *might* need to be ported, but it's generally not practical to worry about that today. In these cases, `int` and `long` are more than adequate. – Mike Harris May 06 '16 at 14:25
  • @luk32 you are correct, I added a paragraph about that –  May 06 '16 at 14:51
  • 1
    @MikeHarris platforms can change over time. The same program developed internally may behave differently if, for example, it is compiled in 32 and 64 bit mode. Perhaps another platform is added to the office: maybe previously only Windows was supported, but now people want to use Linux or MacOS, maybe on a non-Intel chip. These are perfectly valid reasons to consider using integer types that have a bounded size. –  May 06 '16 at 14:53
  • 1
    @Snowman Oh, I absolutely agree that things can (and do) change! But many places would rather focus their efforts on the task at hand, rather than worry about what might (or might not) happen in the future. I've worked in places that had 20-year-old Solaris applications running that no one bothered to port to a modern Linux distribution. – Mike Harris May 06 '16 at 15:24
  • 1
    @MikeHarris it takes all of a few seconds to pick the right data type, which a developer is already thinking about anyway. If the choice is `int` or `long` or `long long`, adding `int32_t` to the list is not a big deal. –  May 06 '16 at 15:45
  • I think you should clarify the difference between `int_leastXX_t` and `int_fastXX_t ` in terms of when you would use them. Currently, it reads that they both provide at least XX bits, but no explanation why I might want to use `int_leastXX_t`, which I believe would provide the *smallest* type that is at least XX bits (i.e. efficient on space). I think it might also be pertinent to provide the version of the C standard in which these types were added ("modern" C is relative). – Casey Kuball May 06 '16 at 19:24
  • 1
    @Darthfett fair enough, I went into more detail. Please note this question is about C++, not C, with C++11 being the relevant standard. –  May 06 '16 at 19:49
  • 1
    @MartinBa If they're ready to ban `long` because it might be 32-bit on one platform and 64-bit on another they should be prepared to ban `int` because it might be 16-bit on one platform and 32-bit on another. If they are willing to disregard that something might be used on an embedded system they should put a warning on the code 'not suitable for systems with 16-bit ints' to stop people complaining when the library generates errors on an Arduino Uno. – Pharap May 08 '16 at 15:52
  • @Paraph - except that *at the moment and the next couple of years* 16bit `int` is totally irrelevant for them, whereas the `long` problem is current now. – Martin Ba May 08 '16 at 19:27
  • It should be noted that **the "exact width" types (`uint16_t` etc.) are optional**, and usually aliases for the `_least_` types where supported. Which makes sense once you realize that `CHAR_BITS` might not be 8 everywhere.... – DevSolar May 09 '16 at 12:35
  • If you don't need an exact width and don't have a particular reason to prefer speed over size or vice versa, then there is no reason to use `int_least16_t`/`int_fast16_t` over `int`, `int_least32_t`/`int_fast32_t` over `long`, or `int_least64_t`/`int_fast64_t` over `long long`. – Nikolai May 09 '16 at 12:43
39

No, banning the builtin integer types would be absurd. They should not be abused either, however.

If you need an integer that is exactly N bits wide, use std::intN_t (or std::uintN_t if you need an unsigned version). Thinking of int as a 32 bit integer and long long as a 64 bit integer is just wrong. It might happen to be like this on your current platforms but this is relying on implementation-defined behavior.

Using fixed-width integer types is also useful for inter-operating with other technologies. For example, if some parts of your application are written in Java and others in C++, you'll probably want to match the integer types so you get consistent results. (Still be aware that overflow in Java has well-defined semantics while signed overflow in C++ is undefined behavior so consistency is a high goal.) They will also be invaluable when exchanging data between different computing hosts.

If you don't need exactly N bits, but just a type that is wide enough, consider using std::int_leastN_t (optimized for space) or std::int_fastN_t (optimized for speed). Again, both families have unsigned counterparts, too.

So, when to use the builtin types? Well, since the standard does not specify their width precisely, use them when you don't care about the actual bit width but about other characteristics.

A char is the smallest integer that is addressable by the hardware. The language actually forces you to use it for aliasing arbitrary memory. It is also the only viable type for representing (narrow) character strings.

An int will usually be the fastest type the machine can handle. It will be wide enough such that it can be loaded and stored with a single instruction (without having to mask or shift bits) and narrow enough so it can be operated on with (the most) efficient hardware instructions. Therefore, int is a perfect choice for passing data and doing arithmetic when overflow is not a concern. For example, the default underlying type of enumerations is int. Don't change it to a 32 bit integer just because you can. Also, if you have a value that can only be –1, 0 and 1, an int is a perfect choice, unless you're going to store huge arrays of them in which case you might wish to use a more compact data type at the cost of having to pay a higher price for accessing individual elements. More efficient caching will likely pay off for these. Many operating system functions are also defined in terms of int. It would be silly to convert their arguments and results back and forth. All this could possibly do is introduce overflow errors.

long will usually be the widest type that can be handled with single machine instructions. This makes especially unsigned long very attractive for dealing with raw data and all kinds of bit manipulation stuff. For example, I would expect to see unsigned long in the implementation of a bit-vector. If the code is written carefully, it doesn't matter how wide the type actually is (because the code will adapt automatically). On platforms where the native machine-word is 32 bit, having the backing array of the bit-vector be an array of unsigned 32 bit integers is most desirable because it would be silly to use a 64 bit type that has to be loaded via expensive instructions only to shift and mask the unneeded bits away again anyway. On the other hand, if the platform's native word size is 64 bit, I want an array of that type because it means that operations like “find first set” may run up to twice as fast. So the “problem” of the long data type that you're describing, that its size varies from platform to platform, actually is a feature that can be put to good used. It only becomes a problem if you think about the builtin types as types of a certain bit width, which they simply ain't.

char, int and long are very useful types as described above. short and long long are not nearly as useful because their semantics are much less clear.

5gon12eder
  • 6,956
  • 2
  • 23
  • 29
  • 4
    The OP called out in particular the difference in the size of `long` between Windows and Unix. I might be misunderstanding, but your description of the difference in the size of `long` being a "feature" instead of a "problem" makes sense to me for comparing 32 and 64 bit data models, but not for this particular comparison. In the particular case this question asked about, is this really a feature? Or is it a feature in other situations (that is, in general), and harmless in this case? – Dan Getz May 05 '16 at 22:34
  • A nasty caveat with the built-in types is that on 32-bit or smaller platforms, uint32_t operations are performed using guaranteed modular wrapping semantics on out-of-range values, but on larger platforms they are performed as signed arithmetic with jump-the-rails-on-overflow semantics. – supercat May 05 '16 at 22:53
  • 2
    @supercat I'm not sure what you mean. Signed overflow is always undefined behavior and unsigned overflow has always modular wrapping semantics. The only thing you cannot rely on is that `unsigned int` will wrap modulo 2^32. It will wrap modulo 2^N for implementation-defined N >= 16. – 5gon12eder May 05 '16 at 23:00
  • 3
    @5gon12eder: The problem is that types like uint32_t were created for the purpose of allowing code's behavior to be independent of the size of "int", but the lack of a type whose meaning would be "behave like a uint32_t works on a 32-bit system" makes writing code whose behavior is correctly independent of the size of "int" much harder than writing code which is almost correct. – supercat May 05 '16 at 23:06
  • @supercat The behavior of `unit32_t` is guaranteed to be the same on every implementation that defines the type. – 5gon12eder May 05 '16 at 23:12
  • 2
    I once spent six months porting software in that had a server and a client portion. `int` was used everywhere, particularly in structs both sides used for communication. Then the server OS went from 16 to 32 bit, and I had to change every damn `int` in the code to a 16 bit data type, all the while cursing the original authors for not choosing a data type whose size didn't change. Their only excuse was that at the time there was no standard sized types. – Gort the Robot May 05 '16 at 23:30
  • 1
    @StevenBurnap Well, I explicitly didn't recommend using `int` as a type where it is important that the bit-width is predictable. But even in the absence of `stdint.h`, the authors of your software should have been using a custom `typedef`. This would have reduced your porting effort to six seconds. – 5gon12eder May 05 '16 at 23:35
  • 3
    Yeah, I know...that was where the cursing came from. The original authors just took the path of lease resistance because when they wrote the code, 32-bit OSes were over a decade away. – Gort the Robot May 05 '16 at 23:37
  • 9
    @5gon12eder Sadly, supercat is correct. All of the exact-width types are "just typedefs" and the integer promotion rules take no notice of them, which means that arithmetic on `uint32_t` values will be carried out as *signed*, `int`-width arithmetic on a platform where `int` is *wider* than `uint32_t`. (With today's ABIs this is overwhelmingly more likely to be an issue for `uint16_t`.) – zwol May 05 '16 at 23:55
  • @zwol Alright, now I see your point. Yes, that's sadly true. Although, as long as all integers have bit widths that are a power of two, I can't see how you would overflow the `int` *with a single operation* that would be sensible on the original fixed-width `unsigned` type. – 5gon12eder May 06 '16 at 00:07
  • 3
    "long will usually be the widest type that can be handled with single machine instructions." given the list of popular platforms today size_t is a better bet for this use. – Peter Green May 06 '16 at 00:20
  • 1
    @5gon12eder: On a compiler for a 32-bit silent-wraparound two's-complement platform, given `uint32_t mult(uint16_t a, uint16_t b) { return a*b;}` what should `mult(50000,50000)` do? If you said "yield 2500000000u" you'd agree with what the authors of the C89 Standard said in their rationale for why unsigned shorts promote to signed int, but such a viewpoint is no longer fashionable and such code will sometimes break on gcc. – supercat May 06 '16 at 03:59
  • 2
    @zwol: To make things even more fun, even on systems where "int32_t" would have the same representation as "int" and "long", it's not possible for "int32_t*" to be compatible with both "int*" and "long*", and storing a value with one integer type and reading it with another is likely to fail even if they have the same representation. – supercat May 06 '16 at 04:03
  • 9
    1st, thanks for a detailed answer. But: Oh dear. Your long paragraph: "`long` will usually be the widest type that can be handled with single machine instructions. ..." - and this is exactly **wrong**. Look at the Windows data model. IMHO, your whole following example breaks down, because on x64 Windows long is still 32 bit. – Martin Ba May 06 '16 at 08:24
  • @supercat I would consider this particular example broken for different reasons. `mult` should first convert the operands to 32 bit types before performing the multiplication. At least if the intended semantics of the function are to yield an exact result for all inputs. Btw, do you have a link handy to that rationale? I'd be interested in reading that. – 5gon12eder May 06 '16 at 14:15
  • @5gon12eder: On platforms where "int" is 16 bits, the code would yield 63744LU of course, but I don't know of any microcomputer compilers before the middle of the last decade (around 2005) that wouldn't behave in the fashion the authors of the C89 Standard described as typical in the rationale describing their choice to have short unsigned types promote to signed int. – supercat May 06 '16 at 14:43
  • @5gon12eder: Perhaps the example would have been more interesting as `uint16_t mul_mod_65536(uint16_t x, uint16_t y) { return x*y; }`. Though I haven't yet found a situation where a 32-bit-`int` gcc would break that one, I merely attribute that to a lack of creativity on the part of gcc's authors [I doubt anyone in 1990 would have expected that a two's-complement machine with 32-bit integer should have any problem with the earlier "mult" function]. – supercat May 06 '16 at 14:51
  • 1
    -1. So much wrong information in your second-to-last paragraph (which is the most relevant to the question). `long` is NOT the “native” size (nor is it the same size as a pointer) in 64-bit MSVC++. And `uint_fast32_t` matches the desired semantics of your bit-vector whereas `long` does not. – dan04 May 06 '16 at 17:02
  • @supercat Thanks for the discussion. It has motivated me to write a [small library](http://codereview.stackexchange.com/q/127803/) to mitigate the problem. – 5gon12eder May 07 '16 at 23:53
  • @5gon12eder: Multiplying any type by 1u, or adding 0u, will coerce the value to `unsigned int` if the original was a small type, or will yield the original value if it was a full-size type, and many compilers are likely to avoid generating machine code for multiplying by one or adding zero. BTW, I don't find the behavior of short unsigned types alarming when the result is used in a way where signed and unsigned values would be expected to differ. What's more alarming is that compilers have started generating bogus code in situations where the behaviors of signed and unsigned types... – supercat May 09 '16 at 14:49
  • ...are identical in all defined cases, and were for for well over a decade since C89 was published treated as identical by all compilers for microcomputer platforms (I'd be genuinely interested to know the first counter-example; the current version of gcc sometimes generates code for "uint1=ushort1*ushort2;" that fails if the result is in the range "INT_MAX+1u" to "UINT_MAX", but I don't know what compiler was the first to do so). – supercat May 09 '16 at 14:52
  • @supercat your example with `50000*50000` is broken: it has UB since it overflows the usual 32-bit `int` (one more problem with such unsigned-to-signed promotion). – Ruslan May 09 '16 at 15:06
  • @Ruslan: If you read the C89 rationale, the authors of the Standard note that on the majority of current implementations, such an the expression would yield the arithmetically-correct result if coerced to an unsigned type. I know of no general-purpose two's-complement implementations produced within 15 years of that where such code would not either yield an arithmetically correct result or trap in a documented fashion, and I know of no reason that two's-complement implementations written by individuals who aren't deliberately obtuse should not have continued such behavior forever. – supercat May 09 '16 at 16:36
18

The only reason I would use long today is when calling or implementing an external interface that uses it.

As you say in your post short and int have reasonably stable characteristics across all major desktop/server/mobile platforms today and I see no reason for that to change in the foreseeable future. So I see little reason to avoid them in general.

long on the other hand is a mess. On all 32-bit systems I'm aware of it had the following characteristics.

  1. It was exactly 32-bits in size.
  2. It was the same size as a memory address.
  3. It was the same size as the largest unit of data that could be held in a normal register and work on with a single instruction.

Large amounts of code was written based on one or more of these characteristics. However with the move to 64-bit it was not possible to preserve all of them. Unix-like platforms went for LP64 which preserved characteristics 2 and 3 at the cost of characteristic 1. Win64 went for LLP64 which preserved characteristic 1 at the cost of characteristics 2 and 3. The result is you can no longer rely on any of those characteristics and that IMO leaves little reason to use long.

If you want a type that is exactly 32-bits in size you should use int32_t.

If you want a type that is the same size as a pointer you should use intptr_t (or better uintptr_t).

If you want a type that is the largest item that can be worked on in a single register/instruction then unfortunately I don't think the standard provides one. size_t should be right on most common platforms but it wouldn't be on x32.


P.S.

I wouldn't bother with the "fast" or "least" types. The "least" types only matter if you care about portablility to really obscure architectures where CHAR_BIT != 8. The size of the "fast" types in practice seems to be pretty arbitary. Linux seems to make them at least the same size as pointer, which is silly on 64-bit platforms with fast 32-bit support like x86-64 and arm64. IIRC iOS makes them as small as possible. I'm not sure what other systems do.


P.P.S

One reason to use unsigned long (but not plain long) is because it is gauranteed to have modulo behaviour. Unfortunately due to C's screwed up promotion rules unsigned types smaller than int do not have modulo behaviour.

On all major platforms today uint32_t is the same size or larger than int and hence has modulo behaviour. However there have been historically and there could theoretically be in the future platforms where int is 64-bit and hence uint32_t does not have modulo behaviour.

Personally I would say it's better to get in the habbit of forcing modulo behaviour by using "1u *" or "0u +" at the start of your equations as this will work for any size of unsigned type.

Peter Green
  • 2,125
  • 9
  • 15
  • 1
    All of the "specified-size" types would be much more useful if they could specify semantics which differed from the built-in types. For example, it would be useful to have a type which would use mod-65536 arithmetic regardless of the size of "int", along with a type that would be capable of holding numbers 0 to 65535 but could arbitrarily *and not necessarily consistently* be capable of holding numbers larger than that. What size type is fastest will on most machines depend on context, so being able to let the compiler pick arbitrarily would be optimal for speed. – supercat May 06 '16 at 12:56
5

Another answer already elaborates on the cstdint types and lesser-known variations therein.

I'd like to add to that:

use domain-specific type names

That is, don't declare your parameters and variables to be uint32_t (certainly not long!), but names such as channel_id_type, room_count_type etc.

about libraries

3rd party libraries that use long or whatnot can be annoying, especially if used as references or pointers to those.

The best thing is to make wrappers.

What my strategy is, in general, is to make a set of cast-like functions that will be used. They are overloaded to accept only those types that exactly match the corresponding types, along with whatever pointer etc. variations you need. They are defined specific to the os/compiler/settings. This lets you remove warnings and yet ensure that only the "right" conversions are used.

channel_id_type cid_out;
...
SomeLibFoo (same_thing_really<int*>(&cid_out));

In particular, with different primitive types producing 32 bits, your choice of how int32_t is defined might not match the library call (e.g. int vs long on Windows).

The cast-like function documents the clash, provides for compile-time checking on the result matching the function's parameter, and removes any warning or error if and only if the actual type matches the real size involved. That is, it's overloaded and defined if I pass in (on Windows) an int* or a long* and gives a compile-time error otherwise.

So, if the library is updated or someone changes what channel_id_type is, this continues to be verified.

JDługosz
  • 568
  • 2
  • 9