Why in stdint.h have both least and fast integer types?

Discussion in 'C Programming' started by GS, Nov 27, 2004.

  1. GS

    GS Guest

    The stdint.h header definition mentions five integer categories,

    1) exact width, eg., int32_t
    2) at least as wide as, eg., int_least32_t
    3) as fast as possible but at least as wide as, eg., int_fast32_t
    4) integer capable of holding a pointer, intptr_t
    5) widest integer in the implementation, intmax_t

    Is there a valid motivation for having both int_least and int_fast?
    GS, Nov 27, 2004
    1. Advertisements

  2. Of course. If 16 bit integers are slow in your hardware, and 32 bit
    integers are fast, then you would want int_least16_t to be 16 bit, and
    int_fast16_t to be 32 bit. That covers about every computer that you can
    buy in a shop.
    Christian Bau, Nov 27, 2004
    1. Advertisements

  3. GS

    James Harris Guest

    Interesting example but what advantage does int_least16_t really give? If
    we are talking about a few scalars wouldn't it be OK to let the compiler
    represent them as int32s since they are faster? If, on the other hand,
    these were stored in arrays
    int_least16_t fred [10000];
    why not let the compiler choose whether to store as int16 or int32,
    depending on it's optimization constraints?
    James Harris, Nov 27, 2004
  4. The stdint.h header definition mentions five integer categories,
    Space savings.
    The programmer asked for memory savings over speed savings by
    using int_least16_t over int_fast16_t. Speed doesn't do much good
    if the program won't fit in (virtual) memory.

    The few scalars might be deliberately made the same type as that
    of a big array (or disk file) used in another compilation unit.
    One example of this is storing data in dbm files using a third-party
    library. When you retrieve data from dbm files, you get back a
    pointer to the data, but it seems like it's usually pessimally
    aligned, and in any case the dbm functions do not guarantee alignment,
    so the way to use it is to memcpy() to a variable/structure of the
    same type, and access it there. This fails if different compilations
    have different sizes for int_least16_t.
    sizeof(int_least16_t) must be the same in all compilation units
    that get linked together to make a program. (of course, array
    subscripting, allocating a variable or array of int_least16_t, and
    pointer incrementing all implicitly use that size) The optimizer
    doesn't get much info on what size to make int_least16_t when the
    only reference to it is:

    void *vp;
    size_t record_count;

    qsort(vp, record_count, sizeof(int_least16_t), compar);

    However, using that information, the compiler *MUST* choose now.
    Perhaps before the part that actually allocates the array vp points
    at is even written.

    Gordon L. Burditt
    Gordon Burditt, Nov 27, 2004
  5. That would create incompatibilities between modules compiled with different
    optimisation settings : a horrible side effect, that would cause unlimited
    headaches !
    My understanding is that int16_t must be exactly 16 bits.
    int_least16_t should be the practical choice on machines where 16 bit ints have
    to be emulated for instance, but otherwise would still be implemented as 16 bit
    ints, whereas int_fast16_t would only be 16 bits if that's the fastest option.

    There really is more than just the speed/size tradeoff: practical/precise is
    another dimension to take into account.
    Charlie Gordon, Nov 29, 2004
  6. GS

    Kevin Bracey Guest

    The point you missed is that the _least types are supposed to be the
    *smallest* types at least as wide, as opposed to the *fastest*, which are
    designated by _fast.

    A typical example might be the ARM, which (until ARMv4) had no 16-bit memory
    access instructions, and still has only 32-bit registers and arithmetic
    instructions. There int_least16_t would be 16-bit, but int_fast16_t might be

    How you decide what's "fastest" is the tricky bit.

    In a function, code like:

    uint16_t a, b, c;

    a = b + c;

    would be slow on the ARM, because it would have to perform a 32-bit
    addition, and then manually trim the excess high bits off. Using
    uint_fast16_t would have avoided that. [*]

    On the other hand, if you had an array of 2000 such 32-bit int_fast_16_ts you
    were working on, having them as 16-bit might actually be faster because they
    fit in the cache better, regardless of the extra core CPU cycles to
    manipulate them.

    That observation is likely to be true for pretty much any cached processor
    where int_fast_XX != int_least_XX, so as a programmer it's probably going to
    be a good idea to always use int_least_XX for arrays of any significant size.

    [*] Footnote - some good ARM compilers have "significant bit tracking" that
    can actually figure out when such narrowing is mathematically
    Kevin Bracey, Nov 29, 2004
  7. GS

    James Harris Guest

    For scalars?
    You expect to run out of memory? If that is really a problem why not use

    More to the point, memory constraints are more likely to be a feature of
    PICs or similar. In that case I would want to be able to tell the compiler
    to fit the code in X words but to still optimize to be as fast as possible.
    Agreed but better, surely, to define the interface using int16_t. I expect
    that int_least16_t would be different for different implementations, making
    them incompatible with each other. This is an argument against the presence
    of int_least16_t.
    Again, perhaps this is better written as int16_t, though I am beginning to
    see there could be benefits to separating int_fast16_t.
    James Harris, Dec 1, 2004
  8. GS

    James Harris Guest

    James Harris, Dec 1, 2004
  9. GS

    James Harris Guest

    But isn't that exactly what int_least16_t does? It requires compilation
    under the same rules for all modules which are to be linked together (and
    that share data). Otherwise chaos will ensue. Given that the compilation
    rules must match why have the three types of 16-bit integer? I can see the
    need for two,

    1) an integer that is at least N bits wide but upon which operations are as
    fast as possible,
    2) an integer than behaves as if it is exactly N bits wide - for shifts

    but I'm not sure about having a third option. This seems a bit baroque and
    not in keeping with the lean nature that is the essence of C. It also seems
    to me to confuse the performance vs. space issue with program logic. Is
    this a set of data types designed by committee? I wonder what Ken Thompson
    and Dennis Ritchie make of it.
    James Harris, Dec 1, 2004
  10. GS

    James Harris Guest

    The *smallest* type as least as wide as 16 is of width 16, no? If it is
    impossible to support an integer of width 16 (18-bit word, for instance)
    how does the implementation deal with this standard's int16_t?
    Absolutely! There is no point making a data type "fast" if it is to be
    repeatedly compared with values which are not the same width. Of course,
    operations are fast or slow, not data values. Is the standard confusing two
    orthogonal issues?
    Yes, I think I'm coming round to having one that behaves as if it is
    exactly 16 bits and another that behaves as if it has at least 16 bits.
    I can see your point here. It's a subtlety. I still wonder, though, if I
    wouldn't prefer to specify that array as int16_t. Specifying int_least16_t
    is making me a hostage to the compiler. If I am taking in to account the
    architecture of the underlying machine (in this case, primary cache size)
    wouldn't I be better writing more precise requirements than int_leastX_t?
    James Harris, Dec 1, 2004
  11. The stdint.h header definition mentions five integer categories,
    Yes. The compiler can't necessarily tell that there aren't malloc'd
    arrays of these things also. Space savings might translate into
    speed savings, too (since you seem to be stuck on fast == GOOD at
    the expense of everything else) due to the operation of data caches.

    Wouldn't it be OK to let the compiler represent int_fast16_t as
    an int16_t on a machine which requires shift-and-mask operations to
    BECAUSE IT TAKES LESS MEMORY? No, because int_fast16_t is supposed
    to be fast. Likewise, int_least16_t is supposed to be small.
    int16_t is not guaranteed to exist at all, although it will
    not be a problem on most current machines. Eventually it might
    be an issue on machines where (char,short,int,long,long long) are
    (32, 64, 128, 1024, and 8192) bits, respectively.
    int_least16_t is a way of telling the compiler to save memory.
    If you want fast, use int_fast16_t.

    It is quite possible for there to be a tight memory constraint on
    some memory but not others in embedded devices, for example, limited
    space for NONVOLATILE memory (represented, for example, as one
    struct containing all the nonvolatile parameters) but more generous
    memory for the program to run.
    int16_t need not exist.
    If the data in question is not used outside the program (as would likely
    be the case with arrays or with temporary disk files used only while
    this program is running, portability of data between implementations
    is not an issue.
    int16_t need not exist.

    I was very disappointed in the standard for not requiring int_least11_t,
    int_fast37_t, and, if it exists in the implementation, int53_t.
    (or, in general, int_leastN_t, int_fastN_t, and if present, intN_t
    for all prime values of N up to the maximum size available, and
    preferably non-prime values as well). It would at least be clear
    in arguments over int_fast37_t vs. int_least37_t that there is a
    good chance that int37_t doesn't exist.

    Gordon L. Burditt
    Gordon Burditt, Dec 1, 2004
  12. Not a very safe way to call qsort().
    I would recommend that vp be of the proper type and be used for the sizeof
    operation :

    int_least16_t fred[10000];
    size_t record_count;
    int_least16_t *vp = fred;

    qsort(vp, record_count, sizeof(*vp), compar);

    It is a pity our favorite language cannot manipulate types with more ease.
    This would allow much safer definitions such as:

    typedef void T; /* T can be any type */
    void qsort(T *, size_t, size_t == sizeof(T), int (*comp)(const T *, const T *));
    /* T can be any type, but parameter consistency can be enforced.

    This kind of template would not require any run time support, and would generate
    generic code, but allows to enforce type consistency, without opening C++
    template Pandora's box.
    Charlie Gordon, Dec 1, 2004
  13. GS

    Flash Gordon Guest

    Only on implementations *having* a type that is exactly 16 bits wide.
    That's simple. It does not define int16_t
    There are generally speed issues which are related to size, such as a
    system with a 32 bit address bus that can quickly access a 32 bit type
    but has to either mask or shift the data to access a 16 bit value.

    Well, when you port the SW to a 32 bit DSP processor that has absolutely
    no support for 16 bit data and therefor does not provide int16_t? Such
    an implementation can easily provide both int_least16_t and
    int_fast16_t, anthough they would both be identical to int32_t
    Flash Gordon, Dec 1, 2004
  14. It probably makes sense to stick with fast variants for scalars. The
    typical use for least variants would be in arrays and structures.
    Because it has no advantages over int_least16_t (except that it is shorter
    to type, and maybe some minor modulo properties) and has a portability
    Tht depends on whether the code or the data is subject to the memory
    constraint (or some combination).
    Using int16_t doesn't fix the representation issues (byte order etc.). To
    do this properly the external data format should be kept separate from the
    representation of any internal datatype. You can do this just as well with
    int_least16_t as int16_t.
    If the compiler has a 16 bit datatype available then this is what it
    should use for int_least16_t. This means that int_least16_t is equivalent
    to int16_t where there is a 16 bit type available, and int_least16_t
    still works where there isn't. Indeed it is difficult to think of a
    situation where using int16_t is a sensible idea.

    Changing the model of object representation based on optimisation issues
    seems a really bad idea even if it could be made to work. Keep it
    simple - use the smallest available type. The programmer should be aware
    that this saves data space, but not necessarily code space. He can then
    make the appropriate judgements rather than having to 2nd guess the
    int16_t doesn't make much sense here, if you are worried about the space
    used by the array use int_least16_t otherwise int_fast16_t. C programmers
    have been doing that for years in a less formal way (the types are called
    short and int). It is an approach that has proved to work very well.

    Lawrence Kirby, Dec 2, 2004
  15. GS

    James Harris Guest

    You mean that (for a PIC) we might want to specify code constraints
    separately from those for the data? Why would just asking the compiler to
    try to fit the code+data image to X bytes not be sufficient?

    Fully agree that anything represented externally to the module is part of
    its interface and requires more precise specifications.
    This is helpful. I'm struggling to accepts these types. My feeling is that
    as a programmer I want the following control,

    1) The ability to define an integer, signed or unsigned, that will hold
    values at least of a certain magnitude - almost but not quite the least_xx
    2) The ability to hint to the compiler/linker that certain parts of the
    code and/or certain data structures (referring to integers of type 1,
    above) should be optimised for size. Otherwise all would be optimised for
    speed. That way, the compiler can represent the data structures and produce
    the code that best fits what is being asked taking in to account how the
    data values are *used* in combination with each other throughout the
    programe and taking in to account the target architecture.
    3) Less often, the ability to define a binary 'integer' which will /behave/
    as a bit string of a fixed length (incidentally, does this provide a
    response to your comment: a situation where requiring an integer of fixed
    size is a good idea - where it is primarily used to represent a binary
    register or a set of flags rather than a number - and where one may want to
    shift the value to the left, for example, and know that higher order bits
    will be shifted out)

    I'm not sure that the C types provide that control - or they provide the
    control another way. They seem to approach the matter from a different
    perspective, one that gives me some detailed control of the mechanism but
    in an unnatural and cumbersome way. As an example, before deciding whether
    a scalar should be least_xx or fast_xx I really need to check how it is
    used and how it is combined /thoughout/ the code. If I want to change one
    scalar type from least to fast or vice versa I need to check through the
    code again to see how that will affect other operations. This is not in the
    way my mind currently thinks. I'd rather the compiler did that for me and
    leave me to express the code's *logic*. On the other hand having both types
    does give me perhaps a greater degree of control. Frankly I'm not sure
    which is best. I certainly don't want to have code with such as "#ifdef
    int16_t" in it unless writing interface definitions - which as noted,
    really require more precision than is provided by the optional types (bits,
    endianness, ranges, representation of negative values).

    Noted. I'll have to consider this one. (Am not sure why it would be hard to
    make work since we deal with ints etc all the time.)
    James Harris, Dec 2, 2004
  16. More to the point, memory constraints are more likely to be a feature of
    Because ROM, RAM, and non-volatile RAM are generally sized separately
    for an embedded device. They are not interchangable on the fly.
    They are also not generally equivalent in cost.

    Using int16_t is sensible where you really need an exactly-16-bits
    representation and don't make the effort / don't want to pay the
    performance penalty to make the code work when you've really got
    more than 16 bits. (That is, put in a lot of "x &= 0xffff;"
    statements to lop off any extra bits, deal with sign extension,
    code shifts specially, etc.).

    int16_t may also make sense for interfaces or external data (although
    endianness is still an issue).
    I'd like to see int_least17_t, int_least18_t, int_least19_t, etc.
    especially for all the prime-valued N <= the maximum integer size.
    Ok, this isn't quite like being able to declare
    int[-4095 .. 4096] x;
    OR int[7 .. 11] x;

    In what way does int_leastN_t not satisfy your requirements, except
    for being locked in to a small number of possible N values likely
    to be available?
    That's int_leastN_t.
    That's int_fastN_t.
    A compiler cannot change the size of types on the fly due to
    optimization considerations when parts of the program are separately
    compiled. All parts of the program that use the type must agree
    on what size the type is. I suppose this is possible in a setup where
    the "compiler" copies the source into an object file, and the "linker"
    compiles the whole mess at once, but I don't know of any implementations
    that do that.
    This is intN_t. I suppose it would be possible for a compiler to
    implement int13_t using masking operations and treating shifts specially
    to make it behave identically to a 13-bit int, while actually using
    16 bits, assuming there is no 13-bit hardware support. It may also
    be so slow as to not be worth ever using it.
    They do provide the control, but perhaps not as fine-grained as to the
    size of the type as some people would like.
    You pretty much ALWAYS need to consider how a type is used everywhere
    if efficiency is a significant consideration. And remember, comparing
    or assigning a int_fastN_t scalar to an element of an int_leastN_t
    array is not necessarily faster than comparing a int_leastN_t to
    an element of an int_leastN_t array, where these two are of different
    size. The same applies to int vs. long or short vs. int.
    If you are doing operations between an array of int_leastN_t and a scalar,
    go with the logic of the code and use an int_leastN_t for the scalar.
    The logic of the code doesn't include speed.
    Nobody is really sure whether "register int" or "int" is better for
    a scalar either.
    Gordon L. Burditt
    Gordon Burditt, Dec 2, 2004
  17. GS

    Flash Gordon Guest


    Please tell me how you will store
    static int i;
    in ROM whilst preserving the C semantics. Also tell me how you will
    store code in RAM whilst the device is completely unpowered. Finaly,
    explain how you will code with a Harvard Architecture device, such as
    the TMS320C2x processors that I used to program in C where the data and
    code are stored in completely seperate address spaces which are accessed
    using completely different instructions.

    I used to regularly work on systems where the were completely seperate
    contraints on the code and data.
    Flash Gordon, Dec 3, 2004
  18. GS

    James Harris Guest

    Agreed. I was thinking of a burned image rather than updateable space.

    It seems to me to approach this from a subtly different angle. Correct me
    if I am wrong but this seems to be saying not just, "at least of a certain
    magnitude," (as I was wanting) but also, "as small as possible and
    convenient on the architecture, but not necessarily exact." This is more
    than a hint to the compiler. It is a demand (if I understand correctly).
    This integer will _not_ be bigger than the predefined at-least-as-big-as-N
    under any circumstances. As such it may inhibit code optimisation rather
    than enhance it unless the programmer is careful to consider how that
    variable or array so declared is used in the code.

    For example, I know that nearly all of my code needs numeric quantities
    that are at least 16-bits so I declare a bunch of integers at least as big
    as 16-bit. That's it. In simple terms, if I am compiling for small size I
    want it to use the smallest integers it can in arrays. If I am compiling
    for speed I want it to use the fastest operations it can. (In reality I
    think the compiliation for /smallest/ size is anachronistic. If I want
    small I normally want to try to fit to a certain size but still want as
    fast as possible.)

    It seems that int_least takes that choice away from the compilation step
    and embeds it, possibly in conflicting ways, in the code. I guess I'm
    suggesting int_least and int_fast should be one and the same. Neither is
    int_exact which is, in fact, useful!

    I don't think so. It is a hack. It is possibly included to satisfy the
    16-bit quantities, faster 32-bit operations model. int_fastN_t is a
    nonsense. It is not faster. It is saying to choose a wider integer for this
    variable or elements of this array if, on this architecture, operations on
    this size of integer are normally faster. It takes no consideration
    whatsoever as to what operations are performed on those integers in the

    Say the world is not the rosy, modern, mainstream, familiar, Intel-ish
    16/32-bit paradigm and we have a machine which performs addition and
    subtraction faster on 32-bit integers and performs multiplication and
    division faster on 16-bit rather than 32-bit values. ie. it has a 32-bit
    adder but only a 16-bit multiplier (to give a 32-bit result). What, then,
    is int_fast16_t? There is no such thing. The fastest operations depend on
    how the values are to be used in the code, particularly in the innermost

    Agreed that the compile/link step needs to be unified to handle this.

    It is intN_t but, sadly, intN_t is optional. If the code is to be portable
    I have to fiddle about with the declaraions or, more likely, use
    int_leastN_t and bitmasks. Again this is nonsense. If I have to use
    bitmasks I may be better going for int_fastN_t and masking those. On the
    other hand, if I use the "faster" integers and bitmask them, while I will
    get faster code on the right hardware I've maybe made a rod for the back of
    true right-sized-word hardware. Maybe a clever compiler could get me out of
    this trap but I'd rather not have been put in it in the first place. And
    then there are all those masks littering the code.

    Valid point. Having said that I think there is a difference here. AFAIK
    'register' is a hint or suggestion and need not be followed. I assume the
    least and fast types are directives.

    I fully agree with the point you mentioned above - ints of sizes other than
    the well known ones. I don't know if they would be used much but they would
    have their place and allow a fine degree of control for someone carefully
    writing transportable code.
    James Harris, Dec 15, 2004
  19. GS

    James Harris Guest

    I was thinking about a code image (including read-only data). Thanks for
    pointing out the separation of address spaces even for that.
    James Harris, Dec 15, 2004
  20. It seems to me to approach this from a subtly different angle. Correct me
    Having the code optimizer determine the size of a type based on
    looking at the code is darn near impossible and I don't know of any
    compiler that can do it. A nearly impossible to bypass hurdle is
    that all parts of separate compilations have to agree on the size
    of a type, so any attempt would pretty much have to defer all code
    generation to what's commonly called the "linking" step. "Object
    code" becomes lightly preprocessed source code. Even with that is
    the hurdle that all programs that use files with these types in
    them (running on the same machine) need to use the same size for
    each type. It's just too much trouble for too little benefit.

    You can have a compiler that lets you determine the size of a type
    based on compiler flags (which amounts to a different implementation).
    There is nothing preventing an implementor (or in an implementation
    with text include files, possibly a user of the compiler) from
    hacking up <inttypes.h> with #ifdefs on COMPILE_FOR_SPEED to change
    what is used for int_leastN_t, and using -DCOMPILE_FOR_SPEED to
    select the implementation. However, you'd better use the same
    option on all parts of the same program. That may mean SPEED vs.
    SPACE libraries, which you may not have source code for.

    What do you mean by "compile for small size" or "compile for speed"?
    Is this controlled by flags passed to the compiler? ANSI C specifies
    no such choice. It doesn't rule it out, either, and I believe there
    are compilers that let you select sizeof(int) from two choices with
    flags. I would certainly want the type sizes controlled independently
    from other optimizations. Type sizes must match across all parts
    of a program. Code optimization need not.

    (There is, for example, one file in the code of a well-known database
    program which is often compiled without optimization because compiling
    with optimization tends to run the compilation step out of swap
    space on typical compilations, and the parser tables generated can't
    really be optimized much anyway. Also, turning off code optimization
    is sometimes necessary because the generated code is broken).

    Forget about the optimization step determining the size of a type.
    It won't happen.
    No, it isn't. And compiling for smallest size data may BE the fastest.
    Consider how processor cache operates. Consider how "swapping" and
    "paging" work.
    Not everyone is a speed freak. And sometimes the programmer knows
    that smaller is faster, especially if it's smaller in memory vs.
    disk file.
    WHAT choices at the compilation step? Forget about the optmization step
    changing the size of a type. It won't happen.
    It doesn't matter how darn fast something is if it won't fit in
    available memory!

    int_exact may be useful but it has the potential for being darn
    slow, too, especially if the implementation decided to synthesize
    all of them not supported directly by hardware: e.g. int17_t done
    with masks.
    The bigger the native int gets, the more likely it is that accessing
    smaller chunks of memory will require inefficient methods to access
    small pieces. I wouldn't be too surprised to see 16-bit-addressable
    machines (with a 64-bit or 128-bit int) using a Unicode character
    set in the future, where accessing 8-bit (legacy ASCII) quantities
    requires a shift-and-mask operation, and accessing a 32-bit quantity
    is no faster than accessing a 64-bit quantity.
    If it avoids shift-and-mask, it's probably faster.
    Forget about the optimizer choosing the size of a type. It's not
    going to happen.

    Most of the operations performed on an N-bit type are moving them
    around, or are of equivalent speed.
    The speed metric I'd probably use for deciding what to make
    int_leastN_t is how fast you can load, store, or assign a quantity
    of the given size. There are a number of operations that are
    generally performed as fast as a load or a store for a given size
    integer quantity. Addition, subtraction, bitwise operations,
    compare, etc. generally qualify, so if you can move that size around
    quickly, you can also do simple operations on it at no extra cost.
    Multiplication and division generally do not fall into this class.

    Forget about the optimizer choosing the size of a type. It's not
    going to happen.
    So as a practical matter, it's not going to happen. Among other
    problems, all the people who distribute proprietary code libraries
    are going to object that "object code" is now too close to source
    code and it makes everything involuntary open-source. Plus, it
    explodes the combinations of code libraries needed: you've got 3
    choices for int_least16_t, and 2 choices for int_least32_t, making
    6 copies of every library.

    And even putting everything in the link step doesn't solve the
    problem: sometimes you need several programs to all agree on the
    type sizes to access data they pass between themselves.
    Well, is it really any better than making the compiler use the type
    of int_leastN_t and bitmasks and shifts? Very few platforms will
    support int17_t natively. Of course, there's not a lot of demand
    for that size, either. I can see reasonable arguments for int48_t,
    though, where 32 bits won't do but 64 bits is overkill. (It may
    be a few years before we hit the 280-terabyte hard disk size barrier).
    A register directive should be followed to the extent that taking
    its address must be diagnosed as an error. This is not something
    that can be left to the optimization stage. Otherwise, the code
    can't tell whether it was followed or not.

    On the other hand, the code CAN tell the difference between
    int_leastN_t and int_fastN_t if they are different sizes: sizeof.

    Incidentally, if you really want to try out different cases, there's
    nothing prohibiting you from using your own typedefs which you can
    conditional as needed. For example, rid_type might be the typedef
    for a record ID in a database, and it can be int_least16_t,
    int_least32_t, or int_least64_t depending on target platform,
    licensing (e.g. the demo version only does 65535 records), and
    application. This also would let you make separate decisions
    for instances of different types, where you know more about how
    the types will be used than the compiler.

    Gordon L. Burditt
    Gordon Burditt, Dec 15, 2004
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.