Search

4.6 — Fixed-width integers and size_t

In the previous lessons on integers, we covered that C++ only guarantees that integer variables will have a minimum size -- but they could be larger, depending on the target system.

Why isn’t the size of the integer variables fixed?

The short answer is that this goes back to C, when computers were slow and performance was of the utmost concern. C opted to intentionally leave the size of an integer open so that the compiler implementors could pick a size for int that performs best on the target computer architecture.

Doesn’t this suck?

By modern standards, yes. As a programmer, it’s a little ridiculous to have to deal with types that have uncertain ranges. A program that uses more than the minimum guaranteed ranges might work on one architecture but not on another.

Fixed-width integers

To help with cross-platform portability, C99 defined a set of fixed-width integers (in the stdint.h header) that are guaranteed to have the same size on any architecture.

These are defined as follows:

Name Type Range Notes
std::int8_t 1 byte signed -128 to 127 Treated like a signed char on many systems. See note below.
std::uint8_t 1 byte unsigned 0 to 255 Treated like an unsigned char on many systems. See note below.
std::int16_t 2 byte signed -32,768 to 32,767
std::uint16_t 2 byte unsigned 0 to 65,535
std::int32_t 4 byte signed -2,147,483,648 to 2,147,483,647
std::uint32_t 4 byte unsigned 0 to 4,294,967,295
std::int64_t 8 byte signed -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
std::uint64_t 8 byte unsigned 0 to 18,446,744,073,709,551,615

C++ officially adopted these fixed-width integers as part of C++11. They can be accessed by including the cstdint header, where they are defined inside the std namespace. Here’s an example:

The fixed-width integers have two downsides: First, they are optional and only exist if there are fundamental types matching their widths and following a certain binary representation. Using a fixed-width integer makes your code less portable, it might not compile on other systems.
Second, if you use a fixed-width integer, it may also be slower than a wider type on some architectures. If you need an integer to hold values from -10 to 20, you might be tempted to use std::int8_t. But your CPU might be better at processing 32 bit wide integers, so you just lost speed by making a restriction to that wasn’t necessary.

Warning

The above fixed-width integers should be avoided, as they may not be defined on all target architectures.

Fast and least integers

To help address the above downsides, C++ also defines two alternative sets of integers.

The fast type (std::int_fast#_t) provides the fastest signed integer type with a width of at least # bits (where # = 8, 16, 32, or 64). For example, std::int_fast32_t will give you the fastest signed integer type that’s at least 32 bits.

The least type (std::int_least#_t) provides the smallest signed integer type with a width of at least # bits (where # = 8, 16, 32, or 64). For example, std::int_least32_t will give you the smallest signed integer type that’s at least 32 bits.

Here’s an example from the author’s Visual Studio (32-bit console application):

This produced the result:

fast 8: 8 bits
fast 16: 32 bits
fast 32: 32 bits
least 8: 8 bits
least 16: 16 bits
least 32: 32 bits

You can see that std::int_fast16_t was 32 bits, whereas std::int_least16_t was 16 bits.

There is also an unsigned set of fast and least types (std::uint_fast#_t and std::uint_least#_t).

These fast and least types are guaranteed to be defined, and are safe to use.

Best practice

Favor the std::int_fast#_t and std::int_least#_t integers when you need an integer guaranteed to be at least a certain minimum size.

Warning: std::int8_t and std::uint8_t may behave like chars instead of integers

Note: We talk more about chars in lesson (4.11 -- Chars).

Due to an oversight in the C++ specification, most compilers define and treat std::int8_t and std::uint8_t (and the corresponding fast and least fixed-width types) identically to types signed char and unsigned char respectively. Consequently, std::cin and std::cout may work differently than you’re expecting. Here’s a sample program showing this:

On most systems, this program will print ‘A’ (treating myint as a char). However, on some systems, this may print 65 as expected.

For simplicity, it’s best to avoid std::int8_t and std::uint8_t (and the related fast and least types) altogether (use std::int16_t or std::uint16_t instead). However, if you do use std::int8_t or std::uint8_t, you should be careful of anything that would interpret std::int8_t or std::uint8_t as a char instead of an integer (this includes std::cout and std::cin).

Hopefully this will be clarified by a future draft of C++.

Warning

Avoid the 8-bit fixed-width integer types. If you do use them, note that they are often treated like chars.

Integer best practices

Now that fixed-width integers have been added to C++, the best practice for integers in C++ is as follows:

  • int should be preferred when the size of the integer doesn’t matter (e.g. the number will always fit within the range of a 2 byte signed integer). For example, if you’re asking the user to enter their age, or counting from 1 to 10, it doesn’t matter whether int is 16 or 32 bits (the numbers will fit either way). This will cover the vast majority of the cases you’re likely to run across.
  • If you need a variable guaranteed to be a particular size and want to favor performance, use std::int_fast#_t.
  • If you need a variable guaranteed to be a particular size and want to favor memory conservation over performance, use std::int_least#_t. This is used most often when allocating lots of variables.

Avoid the following if possible:

  • Unsigned types, unless you have a compelling reason.
  • The 8-bit fixed-width integer types.
  • Any compiler-specific fixed-width integers -- for example, Visual Studio defines __int8, __int16, etc…

What is std::size_t?

Consider the following code:

On the author’s machine, this prints:

4

Pretty simple, right? We can infer that operator sizeof returns an integer value -- but what integer type is that value? An int? A short? The answer is that sizeof (and many functions that return a size or length value) return a value of type std::size_t. std::size_t is defined as an unsigned integral type, and it is typically used to represent the size or length of objects.

Amusingly, we can use the sizeof operator (which returns a value of type std::size_t) to ask for the size of std::size_t itself:

Compiled as a 32-bit (4 byte) console app on the author’s system, this prints:

4

Much like an integer can vary in size depending on the system, size_t also varies in size. size_t is guaranteed to be unsigned and at least 16 bits, but on most systems will be equivalent to the address-width of the application. That is, for 32-bit applications, size_t will typically be a 32-bit unsigned integer, and for a 64-bit application, size_t will typically be a 64-bit unsigned integer. size_t is defined to be big enough to hold the size of the largest object creatable on your system (in bytes). For example, if size_t is 4 bytes, the largest object creatable on your system can’t be larger than the largest number representable by a 4-byte unsigned integer (per the table above, 4,294,967,295).

By definition, any object larger than the largest value size_t can hold is considered ill-formed (and will cause a compile error), as the sizeof operator would not be able to return the size without wrapping around.


4.7 -- Introduction to scientific notation
Index
4.5 -- Unsigned integers, and why to avoid them

218 comments to 4.6 — Fixed-width integers and size_t

  • sansan

    "any object larger than the largest value size_t can hold is considered ill-formed (and will cause a compile error)"
    On my machine sizeof(size_t) is 4 bytes.
    But isn't "long long" 8 bytes?

    Thanks in Advance!

  • Henry

    I dont understand what the difference between int_least16_t and uint_fast16_t is. Can you explain what do they both do? I read up something on other websites but still dont understand.

    • Keshav

      for int_least_16_t

      int_least8_t
      int_least16_t
      int_least32_t
      int_least64_t

      smallest signed integer type with width of at least 8, 16, 32 and 64 bits respectively
      (typedef)

      for uint_fast16_t

      uint_fast8_t
      uint_fast16_t
      uint_fast32_t
      uint_fast64_t

      fastest unsigned integer type with width of at least 8, 16, 32 and 64 bits respectively
      (typedef)

      • Henry

        But why uint_fast16_t has 32 bits? And when do I use them?

        • keshav

          unit_fast16_t is not of 32 bits,it is of 16 bits.
          well,i also don't know how to use them

          • nascardriver

            `uint_fast16_t` is at least 16 bits wide, but can be wider if it wants to. If a 32 bit integer is faster, it will be a 32 bit integer. If a 64 bit integer is faster, it will be a 64 bit integer.
            `uint_least16_t` is also at least 16 bits wide, and if your compiler supports 16 bit integer, it will be a 16 bit integer. Only if your compiler doesn't support 16 bit integers, it will be wider.

  • Raj Kolamuri

    I think it would be helpful for others if you mention:
    Fastest signed integer is basically that no. of bits(according to the specific computer) integer that interacts with the computer fastest with atleast specified no. of bits(user given).

  • Math

    Hii

    I find this lesson kind of complicated :( is it okay if I don't understand everything fully for now ?

  • Innervate

    Is it necessary to use the "std" namespace when defining fixed-width integer data type in C++17? For example, I was able to run the following without any issues (might be because I am using visual studio):

    • nascardriver

      Some headers in C++ are backwards compatible with C (which didn't have namespaces), so you can use their contents without `std::`. Since we're not in C, we recommend using the `std::` prefix.

  • sami

    "std::size_t is defined an unsigned integral type, and it is typically used to represent the size or length of objects."

    shouldn't it be "std::size_t is defined AS an unsigned integral type.."?

  • sami

    in fixed-width integers, how come does the architecture of CPU no loner matter? what if there would be a limitation on a specific CUP that couldn't support Fixed-width integers?

  • sami

    "A program that uses more than the minimum guaranteed ranges might work on one architecture but not on another."

    With these days modern standards, why still is there such limitation?

    I mean is this limitation still because there might be some CPU architectures out there that can't support more than the minimum guaranteed ranges and have limitation about that?

  • Cheeks

    "The fixed-width integers have two downsides: First, they may not be supported on architectures where those types can’t be represented. They may also be less performant than the built-in types on some architectures."

    I don't understand what that means. Like x64, x32 or like Mac/Windows? I've been following through all the tutorials but I feel like this one assumes a certain knowledge the average person learning to code may not understand.

    • nascardriver

      hi!

      I adjusted the sentence in question to hopefully make it easier to understand.
      It's not possible to name an architecture, because the availability of the type is decided by the implementation (Compiler + standard library), not the architecture (Although, they often go hand in hand). Even if you're on a 64 bit system, an implementation might not provide a fundamental integer that is 64 bits wide (Usually `long long` is 64 bits wide, but it can be narrower). If no such integer exists, you don't get the fixed width types for it.

  • Marius

    What is "std::uint8_t"? You never ìntroduced what that means.

    Thank you

  • David Slaughter

    In the fixed-width integers section, for what reason are you using direct initialization over the suggested best practice brace/uniform initialization?

    Roughly when was brace initialization considered best practice?

    • nascardriver

      The lesson now uses list initialization as recommended, thanks for pointing out the inconsistency!
      List initialization was added in C++11 with a fix for `auto` in C++14 (I think). The majority of developers don't use list initialization, so I wouldn't say it is considered best practice generally. List initialization is superior to the other initializations, but not (yet) widely popular.

      • Alex

        There are two types of best practices. Those that are superior in method (e.g. they produce better results than than the alternatives), and those that are standard ways of doing things (and doing otherwise would go against expectation).

        List initialization is the first kind of best practice.

  • Luismi

    In the penultimate paragraph, it says "largest number representable by a 4-byte unsigned integer (per the table above, 4,294,967,295 bytes)." I believe the last word in the parenthesis, "bytes", should not be there.
    I also want to send a big thank you to you guys for your effort and time put into creating learncpp.com! It's amazing!

  • saj

    Can somebody help me clear this doubt:

    Suppose I have two computers, computerA, and computerB.
    let's say I created test.cpp on both computerA, and computerB.

    test.cpp :

    I compiled both files seperatly in computerA, and computerB. I named both executable files as cmpA.exe and cmpB.exe, i ran both executables, and the output was:

    My question is, What would be the:
    Output of cmpA.exe in computerB? (would it still overflow and give undefined value?).
    Output of cmpB.exe in computerA?

    • nascardriver

      Assuming both computers are able to understand either binary:

      > Output of cmpA.exe in computerB?
      Same as on computerA.

      > Output of cmpB.exe in computerA?
      Same as on computerB.

      Types exist only in code (With few exceptions). Once you compile the code into a binary, the types are gone. The instructions in the binary tell the computer to use a 16 bit (2 bytes) register, so that's what the computer will do.
      It's likely that your compiler optmized away `intX`, so the overfloat occurred during compile-time already. This means you should get the same undefined number on both computers.

  • Debasish Gogoi.

    When do we actually need to use size_t?

    • Alex

      If you want to store the results of a sizeof() call in a variable, that variable should ideally be of type size_t.

      size_t is also used in a lot of places in the standard library. So if you see it in reference documentation, you'll know what it is.

  • Chayim

    What does < fast > integer function exactly do? For what is it called "fast"?
    Why is int8_t treated as char? What do you mean by "due to an oversight”
    What does < t > at the end of int8_t and size_t stand for? Does it stand for "type"?

    • nascardriver

      > For what is it called "fast"?
      `int*_fast_t` are the fastest integer type that is at least * bits wide, even if that means that the type will resolve to a wider type. If you request a `std::int_fast32_t` but a 64 bit integer is faster on your system, you'll get a 64 bit integer. The "least" types on the contrary will try to prevent being wider than requested.

      > int8_t
      I don't know what Alex meant by "due to an oversight". `int8_t` is commonly an alias for a `signed char`. Remember that `char` is an integral type.

      > What does < t > at the end of int8_t and size_t stand for? Does it stand for "type"?
      Yep, "type".

      • tweedurlee

        Does the compiler do a test to which type of integer is faster(e.g., 8 bit, 16 bit, etc.)? In other words how does your computer know which type of int is the faster than the other? Thank you :)

        • nascardriver

          The standard library implementation and compiler can work together to figure out what the fastest type is. Taking libstdc++ (The standard library that ships with gcc) as an example, the choice is made using a simple preprocessor macro

          libstdc++ unconditionally assumes that `signed char` is the best fit for `int_fast8_t`. For all other types, it uses 32 bit integers if you're compiling as 32 bit (with the exception of `int_fast64_t`, because it can't be 32 bits wide), or 64 bit integers if you're compiling as 64 bit.

  • salah

    As mentioned in the lesson: The fast type (std::int_fast#_t) provides the fastest signed integer type with a width of at least # bits (where # = 8, 16, 32, or 64). For example, std::int_fast32_t will give you the fastest signed integer type that’s at least 32 bits.

    Now let's assume the fastest integer 64 bits.In this case
        std::cout << sizeof(std::int_fast8_t) * 8;
        std::cout << sizeof(std::int_fast16_t) * 8 ;
        std::cout << sizeof(std::int_fast32_t) * 8 ;
    the output must be : 64 64 64 right??, since the fastest one is 64 bits, but the result in my computer was 8 , 16 , 32 , so which one is the fastest one ?

  • Lord Voldemort

    What is the address width of the application? Is it like, the 32-bit architecture will have 32 digits in its address?

  • Shanzeh Hussey

    Is it possible to create a data type using structs with size more than the range of size_t? If no, then why And why it is a compilation error?

  • Scarlet Johnson

    Hey, it is mentioned in this tutorial that "The least type (std::int_least#_t) provides the smallest signed integer type with a width of at least # bits". What is mean by the smallest signed integer here? I've searched a lot on Google but I didn't any information beyond this! And how it is different from int#_t since it also uses at least # bits too.

    • nascardriver

      int#_t is exactly # bits wide. int_fast#_t and int_least#_t are at least # bits wide, but can be wider.
      int_fast#_t uses the fastest type available.
      int_least#_t uses the narrowest type available.

      example
      int16_t is always a 16 bit integer, but this type might not exist.
      int_fast16_t might turn into a 64 bit integer, because that's the fastest on your system.
      int_least16_t might turn into a 32 bit integer, because your system doesn't have a 16 bit integer, but 32 is less than 64.

  • Bruno

    Suuuper small typo BUT i'll point it out anyway :D.

    On the first waning you missed a ' - '

    "Warning

    The above fixed (you missed me Alex) width integers should..."

    Why not make such a good tutorial more perfect. :p

Leave a Comment

Put all code inside code tags: [code]your code here[/code]