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 that wasn’t necessary.


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++.


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:


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:


Much like an integer can vary in size depending on the system, std::size_t also varies in size. std::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, std::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 std::size_t is 4 bytes wide, the largest object creatable on your system can’t be larger than 4,294,967,295 bytes, because this is the largest number a 4 byte unsigned integer can store. This is only the uppermost limit of an object’s size, the real size limit can be lower depending on the compiler you’re using.

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
4.5 -- Unsigned integers, and why to avoid them

270 comments to 4.6 — Fixed-width integers and size_t

  • Humam

    This lesson really confused me about the idea behind it, and the only question I can think of throughout this is "Why?"

    Ok I learned that types do not necessarily have the same range on all architectures.
    I saw that we can use fixed-width integers to guarantee our range on all architectures, but not really guaranteed at the same time as they have their own aforementioned downsides.

    We then enter fast (for performance) and least (for memory conservation) int types, but up until now, I read it at least 3 times over and over again and I feel there is something lacking which I really can't point my finger at because I don't understand the CONCEPT and REASONING of these two types and why they are there.

    Kindly help me understand this...
    least is used to have a FIXED int type of AT LEAST 8, 16, 32, or 64 bits (We will consider our values to be at least within that range, right?).
    Why is it considered memory-saving, while we can use int for 8-bit (Since std::int8_t is dangerous), and long for 32 bit and long long for 64 bit? Are these types used instead of the others I mentioned? What is exactly the point behind these this type and fast type? Why is int_fast16_t the same as int_fast32_t? What varies the "Architecture"? Sorry for the questions I just feel like this particular chapter missed on some things or maybe I'm just a potato at this point in time. Maybe I need to read about 32-bit vs 64-bit applications? Because I generally don't have decent knowledge about them yet, despite the fact that it may be small or so.


    • nascardriver

      We have a desire to use types with an exact size for networking and storage, probably more. Fixed sizes make those topics easier. In those cases, where we really need an exact size, we say "This code only works on architectures X, Y and Z" (The architecture describes how your CPU works, you might have heard of x86 and ARM. Those are architectures.), or we manually manage the memory without using types.

      Most of the time, we don't care how wide a type is, as long as we know that it can hold all of the values that we think are possible in our program. For example, we might have 16 flags for something, so we need at least 16 bits. If the computer gives us 32 bits instead, that doesn't matter, we can still store all of our flags. Rather than saying "This code only works on architectures X, Y and Z", we allow the compiler to give us more memory than we need.

      If a fixed-width type (`std::int*_t`) isn't available (For compatibility, we never assume that these are available), we have to use a type that's wider.
      Now we have 2 choices, `std::int_fast*_t` and `std::int_least*_t`. Both are at least * bits wide, guaranteed.

      If we want to waste as few memory as possible, maybe because we're developing for an embedded system, we'll use `std::int_least*_t`. It's the smallest type that is at least * bits wide. If your compiler has a * bits wide type, that will be used. Otherwise it will chose the next larger type.

      If we're developing for a desktop system, we have plenty of memory and we'd rather have our program be fast. We use `std::int_fast*_t`. It might take up more memory than a `std::int_least*_t`, but it's the type which our compiler thinks can be processed the fastest (If you use a `std::int_fast8_t`, but get a 64 bit int, because your computer is better at calculating with 64 bits than with 8).

      I hope I could help you understand these types a little better. If you have any more questions, please ask.

      • Humam

        If I understood correctly:
        - We set the bits of fast and least based on what we believe our program will hold.

        - least will be used so that compilers or other architectures will see that type and process it as whatever that architecture has and if it doesn't have that least type then it will process it as the next larger type.
        example: std::int_least8_t x(243); This type will be processed as 16 bits on an [INSERT EMBEDDED DEVICE HERE] that can only process at least 16 bit, right?
        But this is mainly focused for embedded devices or so, right? If my focus is on desktop programs, then I should be leaning towards fast more.

        - fast is used so that compilers or other architectures will see that type and process it based on what it can process best (32 bit or 64 bit).

        - I'd rather just use fast types since I'd barely consider memory limitations on my desktop.

        Ok so if I specified which bits I choose for least or fast, it wouldn't matter? because it will eventually get replaced by the other architecture's "settings", it is more or less like a notice for the other architecture that I'm gonna use at least * bits or that I need to run decently at * bits at least. So I should just choose what I believe my program would hold.

        Is that it?

        • nascardriver

          The compiler chooses the type at compile-time. I didn't write that too clearly. Since you have to recompile your program for each architecture, this doesn't change anything.

          > std::int_least8_t x(243);
          Ignoring that 243 doesn't fit into 8 bits, you're right. If the architecture's narrowest type is 16 bits, `x` will be 16 bits wide.

          > If my focus is on desktop programs, then I should be leaning towards fast more.
          Yes. Unless you need to preserve memory for other reasons. Maybe you want to store a ton of 8 bit values. The wasted memory will quickly pile up.

          > fast is used so that compilers or other architectures will see that type and process it based on what it can process best

          > Ok so if I specified which bits I choose for least or fast, it wouldn't matter?
          You choose what you need. If you need 16 bits, don't use a 32 bit int. Maybe 16 bits is the best width for your architecture, you just degraded your program. Let the compiler do it's job.

          > So I should just choose what I believe my program would hold

          Imagine you're going to a car dealership. The dealership has many cars, but they don't have every car that exists.
          If you know exactly what car you want, you tell the dealer and he sells you the car. If they don't have that exact car, you can't buy a car.
          If you don't know exactly what car you want, you tell the dealer what you're looking for. "I want a car that fits at least 4 people and is fast". The dealer gives you whatever fits your needs best, considering where you live (A desert needs other cars than mountains).
          The dealer doesn't ignore what you say. If you lie the them, you'll get something worse than you could have gotten.

          You -> You
          Dealership/Dealer -> Compiler
          Cars -> Types
          Where you live -> Architecture

          • Humam

            Right, so based on the best optimization and usage expectancy I could think of, I will define my types. I understood now.

            Maybe the next questions are a little too advanced for me at this point but I'll just ask it and see if I can fully understand the picture only:

            - So let's say I have already compiled a final .exe of my program. Once it runs on a client who received the file from me, his computer should be able to tell what's best and how it will run my programs based on the types I defined inside, right? I mean, there isn't anything done from my end other than defining the types I see fit best for my program, correct?

            - In the case of INSTALLERS, do they actually TAKE INTO ACCOUNT (BY ME) the system that the program is currently being installed on, and modifies its memory usage based on it? Or are they simply hard-coded into my final product, that if there was any sort of serious incompatability (a fault from my end perhaps) or really old system architecture, it will simply not run? Are the errors given by the installer due to them not working, system errors from the target architecture itself? Or is it a bunch of assertions and exceptions (Both of which I don't know about yet btw) placed by me in the case of serious incompatability with possibly any architecture?

            On the other hand, I understaood the point of this lesson, and these questions are just a little extra to add up to my knowledge about how these things work. Maybe I will not be able to comprehend the answer yet but it would be nice to know.

            Thanks for explaining regardless and apologies for taking this too long.

            • nascardriver

              > So let's say I have already compiled a final .exe
              The exe never changes. The compiler chooses the types at compile-time. No matter who uses that exe, the types are the same. Pretty much all desktop systems run on the x86-64 architecture, that's why you can share your exe without having to recompile it. If you want to run your program on a Raspberry Pi for example, you'll have to recompile it. That's when the types might change.

              > INSTALLERS
              I haven't used windows in a long time, I can't answer this question. Don't you have to chose the version of your OS when you download an installer? I guess the installer checks which version of Windows you're running, then downloads the appropriate binaries.

              • Humam


                Thanks for the answers yet again.

                • Suyash

                  I loved this wonderful and insightful discussion between both of you... The questions that you asked not only cleared unsaid doubts but the exposition provided by @nascardriver (or Alex?) was a delightful read...

                  I am a CS major and while I do understand a lot of these topics (likes 2's complement, overflow, wrapping, etc) and has programmed in C++ before... I learned to program in C++ but when I was taught on C++-03 standards which means I don't have a clue on the modern C++ standards (C++11 or greater) or the best practices...

                  By profession, I am a Python (and bit of JavaScript) programmer with experience in doing backend for web-based applications.

                  I never thought that I will ever code in a system's language again but I was inspired to give C++ another go after watching an amazing interview of Bjarne Stroustrup...

                  And, I have to admit that I am really loving the approach undertaken by the team behind this site (or is this site a single person endeavor) and believe to be one of the best resources on the Internet to learn modern C++.

                  • nascardriver


                    Alex and me are different people. Alex wrote the majority of the lessons.
                    I hope you'll enjoy the lessons, even when they get more C++ specific. Have fun!

  • giang

    Thanks for the helpful lessons. But I wonder, why there're 8 bits (1 byte), 16 bits (2 bytes), 32 bites (4 bytes) or 64 bits (8 bytes) but there' re no 24 bits (3 bytes), 40 bits (5 bytes),...

    • nascardriver

      Your computer works with powers of 2. It can only perform operations on data with a size that's a power of 2. If you had a type with a different size, your computer would have to take up more memory in order to be able to work with it. eg. a 40bit variable would have to be stored in a 64bit variable.

  • HolzstockG

    So when we have these objects such as int, long etc. they can have on another architectures different sizes. Since C++ compiles source code into object files, then every size of a variable that is compatible with certain architecture must be set in compiler dedicated for that machine, right?

    If it is as I said above then how did they manage to create objects such as "least" and "fast" that can omit it?

    Thank you in advance for reply.

  • HolzstockG

    What's the difference between 32-bit application and 64 one? Can someone give a good explenation or a good source to learn from that is not written in very hard to understand way?

    • nascardriver

      Register width. Registers are little variables, there's a limited number of them. They exist from when your program starts to when your program ends. They are re-used constantly. On 32-bit, they are 32 bits wide, on 64-bit, they are 64 bits wide.

      Addressable memory. Registers are used to address your memory. On 32-bit, you can address as most 2^32 bytes = 4GiB of memory. On 64-bit, you can address 2^64 bytes = 16EiB (~17 million terrabytes).

      Assuming x86-64, which probably is what your desktop computer is used, you also have new instructions in 64 bit. Instructions are commands that tell your processor to do something (Add numbers, jump somewhere, compare something, etc.).

  • Maverick9107

    What's the difference between int#_t and int_least#_t ? Can anyone explain it in simple terms. And what is int_fast#_t ? Is it that the machine can compute them faster than int#_t ? Sorry if the question seems a bit dumb.

    • > int#_t
      Not guaranteed to exist, don't use it. Has a guaranteed size of # bits.

      > int_fast#_t
      The fastest type with at least # bits. If a wider integer exists that is faster, that will be used.

      > int_least#_t
      An integer with at least # bits whose size is closest to # bits.

      Use int_fast#_t if you need performance. Use int_least#_t if you need memory efficiency. Use `int` if you don't care about the size.

  • Jeffrey Liu


    I have a question about the fast/least types. In the introduction, it is mentioned, "it’s a little ridiculous to have to deal with types that have uncertain ranges." I understand the point of fixed-width integers, as they are setting a specific size. However, in the case of the fast/least types, are we not making the ranges uncertain again? My understanding from this article is that both regular integer types, as well as fast or least, both specify a minimum number of bits. So, my question is, what is the difference? Or, in other words, what makes the fast/least type an alternative of the fixed-width type, as the number of bits can still vary from computer to computer?

    Thank you!

    • Hi Jeffrey!

      Unless you're transferring binary data between applications or machines, you usually only need a guarantee for the minimum size.
      If you need 32 bits and get 64, that's fine too, your program will still work.

      The `std::int*_t` types are superior to the `std::int_fast*_t` and `std::int_least*_t` types, but they're not necessarily present, which makes they rather useless. You shouldn't use something that might not compile somewhere else.

      Why wouldn't you use the fundamental types (short long int) if you need a minimum size?
      For one, you need to remember the size of those or look it up. The fast and least types have the size in their name, which makes them much more convenient.
      Further, the fundamental types might be slower that their corresponding fast types.
      For example, if you're compiling on 64 bit with gnu c++, the 16, 32, and 64 fast types all resolve to a long int (64 bit), because 64 bit assembly is good at dealing with 64 bit types.
      You're wasting a little memory, but you're getting what you asked for, "fast" types.
      The least types should be the same as the `std::int*_t` types, unless your implementation doesn't support the `std::int*_t` types, in which case they'll grow to whatever is supported.

      • Jeffrey Liu


        Thank you for the clarification! So if I'm understanding correctly, the fast and least are not actually fixed-width? I think my confusion stemmed from the phrasing of this line, "Favor the std::int_fast#_t and std::int_least#_t integers when you need a fixed size guaranteed to be at least a certain size." I was confused because I was under the impression that a fixed size meant that the size had to be the one specified and would be the same for all machines, although this is clearly not the case for fast and least. So another question - does fixed-width mean the same thing as fixed size?

        Thank you!

        • > does fixed-width mean the same thing as fixed size?
          Yes, "fixed" should be removed from the sentence about the fast and least types.

          "Favor the std::int_fast#_t and std::int_least#_t integers when you need a size guaranteed to be at least a certain size."
          Still sounds weird with "size" being there twice, that's up to Alex though.

  • Anthony

    I am all sorts of confused about using the 'correct' type for array indexing, size_t, ssize_t, ptrdiff_t, etc_t...

    In fact, I am so confused, that I need to pick, almost at random, an entry point into this subject. So, let me cite this answer from, which you mention in the discussion here. They're talking about 16-bit architectures:
    "For this reason, the C standard requires (via required ranges) ptrdiff_t, the signed counterpart to size_t and the result type of pointer difference, to be effectively 17 bits."

    So, let's talk about ptrdiff_t, a signed type. If we subtract two pointers, we may get a negative value. This I understand. But does this mean that in a 64-bit address space, we could (theoretically) have the following pointer calculations:

    0 - 18446744073709551615 = -18446744073709551615


    18446744073709551615 - 0 = 18446744073709551615

    A type that goes all the way from -18446744073709551615 up to 18446744073709551615 would surely require 65 bits! OK.. Please explain to me what's going on, because I'm pretty sure that ptrdiff_t isn't 65 bits.

    And this is only the start of my confusion! I'm trying to write a container class that conforms to STL norms, and hence uses size_t for object sizes AND array indices. It has provoked many questions.


    • Unsigned integers can quickly lead to trouble, so I recommend avoiding them. This is the type used by standard containers. You're doing nothing wrong using it for arrays, but it might add extra work further down the road.

      Doesn't exist. If you have it, don't use it.

      A signed integer that can store the difference between 2 pointers. Note that pointer subtraction can only be performed on 2 elements of the same array. Your example is still theoretically possible if an array of single-byte elements takes up the entire memory. `std::size_t` must be at least 64 bits. To perform a subtraction (0-x), `std::ptrdiff_t` would indeed need to be at least 65 bits in size. Here's the catch, apparently `std::ptrdiff_t` can be less than 65 bits (According to cppreference and the GNU implementation). I couldn't find anything in the standard justifying this. As I see it, `std::ptrdiff_t` should have a bigger width than `std::size_t`. If anyone has more information on this topic, please share it.

      • Anthony

        On my Windows 10 64-bit system with VC++:

        predictably enough yields 8 (bytes). As does size_t. It's all very odd.

        I'm thinking of writing my STL-style container using plain old ints for indices and counters. In fact, I just changed the code around, and I can get most of the warnings to go away providing I use the occasional cast to long unsigned int. If I judge the code purely by the amount the compiler complains, this way of doing things seems a lot less hassle. Stroustrup did point out that using unsigned types for sizes and indices in the STL was an innocent, somewhat naive mistake.

        The problem is that if I do use size_t, basic arithmetic and comparison operations where 'STL code' meets user-implemented code become very tricky and error-prone. This is probably going to be my excuse when someone asks me why I'm using an int instead.

        As ever, thanks for your help, nascardriver.

        • Don't use an `int`, `int` has few guarantees about its size. Although there's some confusion about `std::ptrdiff_t` at the moment, it's the better choice.
          Don't use the type directly, use a type alias

          This allows you to easily swap the type you're using and allows users of your container to use the correct type, even after you change it.

          > occasional cast to long unsigned int
          Array indexing requires a type convertible to `std::size_t`. `long unsigned int` is always convertible to `std::size_t`, but using `std::size_t` directly is what you want to do.

          • Alex

            IMO, this topic is C++'s roughest edge right now. The fact that built-in arrays use signed indices but the standard library containers expect unsigned indices leads to inconsistency. Combined with the recommendation to avoid unsigned variables, this is a bit of a mess.

            If you look at the GSL library, they typedef gsl::index type as std::ptrdiff_t and then use gsl::index for anywhere an array index is expected. IMO, that's the best option we have available right now.

            Also note that indicates, "If an array is so large (greater than PTRDIFF_MAX elements, but less than SIZE_MAX bytes), that the difference between two pointers may not be representable as std::ptrdiff_t, the result of subtracting two such pointers is undefined.". This is a concession to the fact that on 64-bit machines, std::ptrdiff_t is probably 64 bits.

            A good rule of thumb is to keep your array sizes under PTRDIFF_MAX elements.

            • > built-in arrays use signed indices
              At their declaration, they use an unsigned integer since C++14, which makes it even more of a mess.

              I tried compiling the following code, which I assumed to be correct

              Of course this code won't run as it exceeds the stack size.
              To my surprise, line 1 didn't even compile. Implementations are allowed to limit the maximum object size. Neither clang nor g++ allow the creation of an object that large, which enables `std::ptrdiff_t` to be 64 bits and still represent the maximum object size in a signed integer.

              The only guarantee about `std::ptrdiff_t`'s minimum size I could find is in C, where it's at least 17 bits.
              I'm still hoping for `std::ssize_t`, which could serve as a proper signed index.

  • David

    Hi, I am using C++17, and it appears I am able to use int16_t, int_fast32_t, etc without having to prefix them with std::,
    or using namespace std. Given that, is it still good practice to use the prefix anyway, as in std::int16_t ?

  • SM.Haider

    The values that sizeof() outputs are in the form of size_t, correct?
    Why wasn't size_t developed as a signed type rather than an unsigned type?

  • Mark

    The last sentence of one paragraph near the bottom states, "...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 bytes)." I believe the inclusion of the word 'bytes' there is a mistype that should be removed. You're just referring to the maximum value of an integer, correct?

    • He's referring to the size of an object and the maximum value of `std::size_t`.
      If `std::size_t`s maximum value is 4'294'967'295, then no object can have a size (in bytes) bigger than 4'294'967'295. The value of the object is irrelevant.

  • James

    This line near the end of FAST AND LEAST, have spelling mistake sir. It's written 'unit'_least. Should be 'uint'.
    "There is also an unsigned set of fast and least types (std::uint_fast#_t and std::unit_least#_t)."

  • Michael D. Blake

    I found something interesting about overflowing.

    So I came to conclusion that our variables will overflow with compile time expressions(I don't know how to put it). I mean assigning variables directly from user input won't make them overflow. Am I correct?

    • @std::cin::operator>> prevents overflows, but enters a failed state if an overflow would have occurred (Covered later). You can't use @std::cin::operator>> after line 10, unless you clear the error flag via @std::cin.clear().
      This is a safeguard of @std::cin::operator>>, overflows can very well occur in other places at run-time.

  • Darshan

    Hey Alex,

    When you are explaining the following
    "Warning: int8_t and uint8_t may or may not behave like chars"

    In the example you gave, shouldn't it be

    instead of

    since you didn't use

    Thank you. You are doing a great work.

  • hailinh0708

    Here's a little hack to force int8_t and uint8_t print an integer (using overloaded operator<<):

    Using this, we can force int8_t and uint8_t to print ints while chars still print well... chars!

    Lesson references:
    9.3 -- Overloading the I/O operators
    7.3 -- Passing arguments by reference

    • Alex

      Neat idea. Here's what actually happening:
      * On most systems, int8_t is a typedef for signed char, and uint8_t is a typedef for unsigned char, but this is not guaranteed.
      * Typedefs don't create distinct types -- they are type aliases.
      * So what you're _actually_ doing is creating overloaded functions for "signed char" and "unsigned char" that print these as integers rather than chars

      While this makes sense for printing int8_t and uint8_t, it doesn't make sense for actual signed and unsigned chars.


      Using your code, this prints 65, not 'A'. That's not good semantically (but maybe not a dealbreaker, as people rarely use the signed char and unsigned char types anyway)

      However, I can think of one downside: it's possible on some machines that char could be an unsigned type -- if that's the case, then uint8_t could actually be typedef'ed to char instead of unsigned char, and this would cause all your chars to be printed as numbers.

      Given that, I can't recommend this as a general "always works" solution, though the above will probably work on the vast majority of machines.

      If you really want an int8_t that prints like a number, the most universal way to do this would be to make a class containing a char, and provide a member function that converts to an int as needed. Then you can let implicit conversion do its thing.

  • Dimitri

    Hi, teacher!

    I'm a little bit confused, but in VS2017 I can print int16_t without #include <cstdint>. Moreover, without std::

    This works fine:

    Is this a good code, or I must use #include <cstdint> and std:: ?

    • Hi!

      * Line 6: Initialize your variables with uniform initialization. You used copy initialization.

      I don't know why it's usable, probably for backward compatibility to C.
      @std::int16_t should not be used. It's an optional type that doesn't have to be available. Use @std::int_least16_t or @std::int_fast16_t.

      • Dimitri

        Thanks, nascardriver! If there is no difference it will be easier to write without @std::

        This is not important, right? If I understood correctly

        • It's available without "std::", because that's how it was in C. This is the case for many types and functions. You should use the version with "std::" to avoid name collisions.

          • Alireza

            As that friend said, It's available without "std::" and without including <cstdint>.
            I haven't understood when "<cstdint>" is useful.

            And what's difference between int_fast_32 and int_least_32 exactly ?
            They both have the value " -2147483648 to 2147483647 and the size of 32-bit ".

            Thank you so much

            • Both, line 3 and 4, cause a compilation error.

              No compilation error.

              Probably what you're experiencing:

              Should cause a compilation error, but doesn't have to.
              Depending on which standard library/includes your compiler uses, it could be that @<iostream> includes @<cstdint>. This is not standard behavior!
              If you're using glibc, the the include trace is

              <iostream> -> <ios> -> <bits/char_traits.h> -> <cstdint>

              According to the standard, there is no @<bits/char_traits.h>, so the includes would end at @<ios> and you won't have access to anything declared in @<cstdint>.
              Don't rely on transitive includes. If you need something, include it.

              > fast/least
              Both, @std::int_fast32_t and @std::int_least32_t have at least 32 bits.
              Both could have more. @std::int_least32_t is as close to 32 bits as possible. @std::int_fast32_t is as fast as possible, even if it means that it takes up a lot more than 32 bits.

              • Alireza

                Hi again,
                I've checked @<iostream>
                @<cstdint> is included in @<bits/char_traits.h> !
                So this is why @int_fast32_t is available without including @<cstdint> and using @std:: .

                You said it's not standard behavior. This is configuration Qt Creator 4.7 .
                Why do they include @<cstdint> in @<bits/char_traits.h> If it's not standard behavior !!!


                • > So this is why @int_fast32_t is available without [...] using @std:: .
                  No, that's because the standard requires these types to be available in the global namespace (without "std::").

                  > Why do they include @<cstdint> in @<bits/char_traits.h> If it's not standard behavior
                  The standard gives a synopsis for all standard header files. Implementations must have the includes that are present in the synopsis, but they are free to add more.

  • Mike

    Hi, I'm abit confuse with all those new command, like int8_t myint = 65;

    I would appreciate if somebody could use int8_t, short int =, in a example of a random small project.

    I'm best at learning from example include reading.


    • Hi Mike!

      They're just like an "int", but can use a different amount of memory. You don't need to understand them yet. But once you work on memory/storage-sensitive applications, you should use appropriately sized variables.

  • mahmoud

    #include <cstdint>
    #include <iostream>

    int main()
        int8_t myint = 65;
        std::cout << myint;

        return 0;


    On most systems, this program will print ‘A’ (treating myint as a char)

    why print A ??

  • Hussaini Magaji

    I am confused, for example if a minimun of 2 bytes can only be allocated for "short" does it mean it can be more than 2 bytes? if so then I think if an "unsigned short" is assigned a number more than 65535 (which is more than the range of an "unsigned short" or 2 bytes interger) no overflow should occur because more than 2 bytes can be allocted for it if the number increases.

    • A short is always a minimum of 2 bytes.
      If your compiler decides that a short is 2 bytes, it will always be 2 bytes and you can't assign values above that range. If your values leave that range, you'll get an overflow.
      If your compiler decides a short is 4 bytes, every short will be 4 bytes.
      You have no control over the size of the native types.

  • Bella

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

    There is probably small mistake... Instead of std:cin there should be std::cin

    Thank you very much for these great tutorials and have a nice day! :)

  • Piotr

    This is so useful! Thanks! You give so much practical information, not only theory.

  • yugin

    What's the difference between a fast type and a least type? Intuitively, I would've thought the smallest possible representation of an integer would also be the fastest since it takes up less memory.

    • Alex

      This is a complicated question that has a lot to do with computer architectures. The short/imprecise answer is that computers are good at moving data of particular sizes. It's like if you have a forklift designed to move boxes that are exactly 1x1x1 foot in size, you can move them fast. However, if you try to move smaller boxes with the forklift, it could actually be a bit more difficult even though the boxes hold less.

      It's the same thing here: a given architecture might be fast at moving 32-bits but less fast at moving 16-bits. Or not -- it really depends on the architecture.

  • Regeta

    the size of long int and int datatypes are same 4 bytes. what is the reason behind it?

    • nascardriver

      Hi Regeta!

      The size of a long int is implementation specific. It is guaranteed the be at least the size of an int or 32 bits, whatever is bigger.

  • Curiosity

    How do I store very large values such as 2^100 or 100! in a variable ?
    Can we do it through classes or structs? If yes, How?
    You just tell me the steps or you know, the algorithm to do it, I will practically ( in C++ ) do it myself !
    Thanks in Advance :)

    • Alex

      There are two ways:
      1) Use a large floating point number (just be wary about precision issues).
      2) Use a custom class that has big number support (use google to find one, there are plenty out there).

      • Curiosity

        Man...? I wanted to create a class for that purpose on my own ! Can you help me in that ?

        • Alex

          Here's a starter approach. C++ gives you 32 bit variables, that can hold values between 0 and 2^x (where x is the number of bits). If you want larger values, you'll have to find a way to decompose those larger values into multiple smaller values (for storage and manipulation). One way to do it would be to use 2 32-bit variables, and combine them to act like a 64-bit variable. You'll need to provide overloaded operators for input, output, and arithmetic.

    • Vlad Alex

      100! Is quite a big number :))))))) but you will have to make an array of uint8_t[1000] and every element would be a digit or make your own class to optimize it even further

  • My dear teacher,
    Please let me this question:
    In name of fixed-width integer, what does last letter (t) mean?
    With regards and friendship

  • Toussaint

    I feel the advice that we should use fixed width integers whenever possible is exactly the opposite of what should be done.

    First, what type of fixed width integers should be used? The exact types (**intN_t**), the fast types (**int_fastN_t**) or the least types (**int_leastN_t**)?

    If you want exact representation (meaning the integer to hold exactly N bits) then use **intN_t**. This is important say in network applications or character encoding and so on. Are there many people writing bit sensitive applications? I suspect not.

    Now, remember that the compiler will add padding to an integer so it fits into memory in order to facilitate fast access by the processor (if needed). There are still architectures out there that are not multiples of 8 or whose natural word length is greater than 8. So sticking to exact fixed width integers without cause will result in a performance penalty because the processor needs to access the exact number of bits you want instead of the number of bits that it is natural for it. Heck, the compiler might even refuse to compile your code for that architecture.
    There are, for instance, machines that were based on 9 bits.

    Also, we have modern machines that are 16 bits instead 8 bits as natural word length. So **int8_t** will not work on them. If you use **int** though, the compiler will add padding so you get 16 bits.

    We have FPGAs that can be configured to have natural word length of 36 bits. So for **intN_t** where N < 36 will simply never work.

    C and C++ prize portability and speed over space usage so please use **int** unless you have a really good reason not to. You will know when you need to if for instance you need to worry about the endianness of the data you are working with.

    Also, neither C nor C++ will do bound checking for fixed width integers even if you use fixed width integers. So you need to be sure in advance that you have the exact size for your data.

    If you badly need to use fixed width integers, first consider using **int_fastN_t** because you are guaranteed that the compiler will try to fit your data into N bits and if it can't, it will adapt to whatever the machine can handle AND you still keep some performance.
    Then if **int_fastN_t** doesn't cut it, consider using **int_leastN_t**. It is the same as **int_fastN_t** but there is no performance guarantee.
    Consider **intN_t** last.

    And last, there is no guarantee that those **intN_t** and friends will even be present on with your compiler depending on the target architecture. They are optional as far the compiler is concerned.

    In short, prefer **int** until it is burden.

    • Alex

      This is a great comment, and I've updated the article recommendations accordingly. Thank you for sharing this perspective.

      • Toussaint

        I thought I'd drop in and hopefully add another useful remark for my fellow C++ learners.
        I wasn't sure if this specific chapter was the right place or the one on loops so I will just do it here since we are talking about integers anyway.

        I would like to talk about integers and looping, specifically the **size_t** type.
        This type is talked about in the chapter on strings, though as a note.
        When we start learning C/C++, we generally use **int** for the loop counter.
        But eventually we realize that we rarely use negative numbers while looping but sometimes we need even greater numbers while looping, so we start using **unsigned int** more and more.
        And as we progress in our journey, we start using the standard library (aka STL) more frequently.
        And for that, we need to loop over strings, containers or other objects which can grow to enormous sizes.

        And there comes **size_t**.

        As mentioned in the chapter on strings, **size_t** is implementation defined.
        And unlike fixed-width integers (which are also implementation defined), this is a good thing (good in the sense we don't have to overthink the issue).
        You see, **int** for example is likely to be limited to a width of 32 bits even on 64 bits architectures.
        An integer with a width of 64 bits will go from **−(2^63)** to **2^63 − 1** while a 32 bits integer will go from **-(2^31)** to ** 2^31 - 1**. So a 64 bits integer is bigger than a 32 bits integer.

        So what happens when you need even greater numbers offered by 64 bits or 128 bits architectures?
        **int** loses its luster!
        Enter **size_t**.

        **size_t** will always hold the greatest unsigned integer that your architecture supports.
        It will hold the size of any object.
        In fact, as of C++14, an object whose size cannot fit into **size_** is ill-formed.

        So my fellow learners, when you do not know in advance the size of your objects ([dynamic] arrays, strings, containers, etc) and you need to say loop over them or store their size, use **size_t**, never **unsigned int**, worse **int**.

        But if you are sure that it will fit into an **unsigned int**, go on ahead. The type must convey the nature of the data anyways.

        And if you really need to loop bellow zero, then by all means **int** is your friends.


  • prince

    i have not included stdint.h header file in my program
    still int8_t data type worked how? i only included <iostream>i am using codeblocks.

    • Alex

      Your iostream may be including stdint.h itself. You should not rely on this, even if it works. Always include all of the headers you need.

  • Jeremy

    This section reminded me of something.

    1) Visual Studio 2017, and Code::Blocks 16.01 don't seem to require the <cstdint> header to use fixed-width integers. Either these have become so popular that they've become part of the C++ Standard (and I looked through the latest draft, and can't find this mentioned anywhere), or most compilers have decided to implement these as "built-in" types. If that's the case, I wonder why the header still exists in those implementations?

    2) A sort of related thing, seeing as how uint8_t and int8_t are usually treated as chars. std::cout prints out char16_t and char32_t as numbers only, and not unicode characters. What's the deal with that, anyway?

    • Alex

      1) I'm not seeing the same thing. I tried the following very minimal program on Visual Studio 2017:

      and it didn't compile. I had to #include cstdint. However, it also compiles if I just #include iostream, so it looks like VS2017's IOstream library is including cstdint. So you're getting those definitions as a rider when you include iostream.

      2) Beats me. I've always considered it a serious flaw that uint8_t and int8_t print as chars. The C++ committee should have mandated that those be treated as separate types with their own numeric handling, not aliases for chars.

      • Jeremy

        That is very strange. I had not thought of removing the include for the iostream header, but it is as you say. Code::Blocks also does it.

        From what I've read about uint8_t and int8_t, a lot of compilers took the lazy route and chose to typedef uint8_t as unsigned char, and typedef int8_t as char. We already have char and unsigned char at our disposal, so I guess as long as it's not specified in the standard, they will take the path of least resistance, lol.

        And having char16_t and char32_t not actually print chars to the screen is also boggling my mind. Maybe it depends on the operating system. I am stuck with Windows, but I wonder if Linux or Mac act differently.

  • Lakshmanan Kanthi

    How does C++ know what int, char and these fundamental data types are? Is it defined in the C++ standard library?

  • Sam

    Hi Alex, if integers were made to change size to fit the needs of the developer, then how does overflow occur? For example, if "int" can range from 2 bytes to ~, and I enter 40,000 (assume it's signed). What stops the integer from simply changing its size to account for the increase. Unless if by size you mean storage space?

    Also, thanks a lot for creating and maintaining this website!

    • Alex

      Integers weren't made to change size to fit the needs of the developer -- they were made to change size to fit the needs of various architectures. So on a given architecture, an integer will always be a fixed size (whatever size is most appropriate for that architecture).

      And by bytes, I do mean storage space (in memory) since with integers the range is directly correlated to the number of bits allocated.

  • sam

    i have an out of topic question...
    when i get bored i try to make some random programs for fun
    so i made this one

    the thing about this program that it will output numbers from 1 to the selected number.
    when i input any big number  (e.g :1500) the output doesn't start from 1 as expected.
    when i input 1500 : the counting starts from 11
    when i input 2000 : int counting starts from 500something
    can you briefly explain to me why is this happening?
    my assumption is that the [std::cout] has a limited output value and its a little bit over 1500 but I'm not sure...

    • John

      I noticed that the "output window" in my IDE only writes out ~200 lines, e.g. if the output is 220 lines long, then only line 20 to 200 will be shown. Your example works as expected when the data is written to a text file.

    • Alex

      x is an int, and should be able to count up to at least 32,767 (but more likely in the 4 millions). I suspect what's happening here is that you are outputting enough lines that the earlier lines are simply scrolling off the console. Your console may allow you to scroll up to see them.

Leave a Comment

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