Portable custom integer width definitions

Discussion in 'C Programming' started by James Harris, Jul 9, 2013.

  1. James Harris

    James Harris Guest

    1. Is there a type for a double-width integer (or a simple way to derive one)?

    I mean that if ints are N bytes then an integer of 2N bytes should be generated.

    I am writing code that I want to compile to 16-, 32- and 64-bit architectures. On each of them the int will be the correct size but I was also looking for a way to define a double-width integer that would be twice as wide as an int.

    If I use type "long" my 16-bit C compiler generates a 32-bit integer but gcc requires "long long" to generate the 64-bit integer that is required on a 32-bit target. The 16-bit compiler doesn't recognise "long long".

    2. Even better than simply doubling the width in all cases is there a clean and portable way to do the following:

    For a 16-bit target generate a 32-bit integer.
    For a 32-bit or 64-bit target generate a 64-bit integer.

    Finally, what names would you use for the resulting wide integer types?

    James Harris, Jul 9, 2013
    1. Advertisements

  2. James Harris

    Noob Guest

    Does your compiler provide stdint.h?

    Noob, Jul 9, 2013
    1. Advertisements

  3. James Harris

    Eric Sosman Guest

    You could check the upper limits of likely candidates:

    #include <limits.h>
    #if LONG_MAX / INT_MAX / 2 == INT_MAX
    typedef long WideInt;
    #elif LLONG_MAX / INT_MAX / 2 == INT_MAX
    typedef long long WideInt;
    #error "No viable candidate"

    Unfortunately, this requires enumerating the candidates -- easy in
    C90, but the set of integer types became open-ended in C99. (Also,
    you should double-check my arithmetic: It'll behave correctly if
    either long or long long is the right choice, but I'm not sure about
    weird cases, like 18-bit int and 45-bit long. Note that a system
    without long long won't define LLONG_MAX, so the second test is
    With a C99 or C11 implementation you could use the <stdint.h>
    header and specify the desired bit widths directly, without all
    this fooling around. If you could live with the base type being
    int16_t instead of int, you could just use int16_t and int32_t
    and avoid all the running around. If the base type actually needs
    to be good old unspecified int it gets messier, but you could march
    through likely candidates as above:

    #include <limits.h>
    #include <stdint.h>
    #if INT_MAX == INT16_MAX && defined(INT32_MAX)
    typedef int32_t WideInt;
    #elif INT_MAX == INT32_MAX && defined(INT64_MAX)
    typedef int64_t WideInt;
    #error "No viable candidate"
    Eric Sosman, Jul 9, 2013
  4. James Harris

    James Harris Guest

    The 32-bit compiler does. It seems that the 16-bit one does not. I did notice that both define types such as __u32. Not sure how portable/standard such names are, though.

    Were you thinking of referring to those in a #if in some way?

    James Harris, Jul 9, 2013
  5. James Harris

    Noob Guest

    I was thinking of using exact-width types, such as
    int8_t, int16_t, int32_t, int64_t
    Noob, Jul 9, 2013
  6. James Harris

    James Harris Guest

    Thanks. Re. the arithmetic I presume it uses integer maths and the results have limited range so I did adapt the expressions slightly. I found it worked mathematically and in code if I used

    #if LONG_MAX / INT_MAX / INT_MAX == 2


    #elif LLONG_MAX / INT_MAX / INT_MAX == 2

    I take it from your comment that if LLONG_MAX is undefined then the second test is defined to return false. It seems to evaluate that way on at least one of the compilers I am using.
    In some instances the base type needs to be int16, int32 or int64.
    Thanks again. The older 16-bit compiler doesn't have those types but I'll try to adapt the preprocessor code to suit.
    If you mean this one, good choice!


    James Harris, Jul 9, 2013
  7. James Harris

    James Kuyper Guest

    To be precise, the expression is evaluated as an intmax_t expression;
    the relevant "limited" range is from INTMAX_MIN to INTMAX_MAX.
    He's relying upon the fact that undefined identifiers are treated as
    having a value of 0 when evaluated in the condition of a #if or #elif.

    You'll have to define what you mean by "N-bit" target. One plausible
    definition: the target is 16 bit if UINT_MAX == 0xFFFF, 32-bit if
    that definition acceptable, or did you have a different one in mind?

    You should also think about the case where your code is ported to a
    platform which doesn't qualify as any one of those three categories.
    It's perfectly legitimate to decide "I don't care what my code does if
    ported to such a platform" - but that should be a conscious decision,
    not the result of failing to pay attention to that possibility. If you
    do care what happens on other platforms, you need to specify what that is.
    Those aren't C standard types. The corresponding <stdint.h> types are
    int16_t, int32_t, and int64_t.
    Are you certain you need exact width types? Would int_fast16_t or
    int_least16_t be acceptable? If so, you should use them, since the exact
    width types are optional.
    James Kuyper, Jul 9, 2013
  8. James Harris

    Eric Sosman Guest

    If you're striving for portability, it'd be a good idea to
    acquaint yourself with the rules of the language you're using ...

    In this instance, Section 6.10.1 of the Standard addresses all
    three of your assumptions or presumptions:

    - Paragraph 4: "[...] After all replacements due to macro
    expansion and the *defined* unary operator have been
    performed, all remaining identifiers [...] are replaced
    with the pp-number 0 [...]" If LLONG_MAX is not defined
    as a macro it survives macro replacement intact and is then
    replaced by zero, leaving `#if 0 / (otherstuff) == 2'.

    - Paragraph 1: "The expression that controls conditional
    inclusion shall be an integer constant expression except
    that [... stuff not altering the integer-ness ...]"

    - Paragraph 4 again: "[...] all signed integer types and
    all unsigned integer types act as if they have the same
    representation as, respectively, the types *intmax_t* and
    *uintmax_t* [...]"
    Then just use int{16,32,64}_t. That's if you need exact widths;
    if you can tolerate extra bits in any of these you'll get more
    flexibility with int_least{16,32,64}_t. For example, if you need
    an exact 16-bit type and a 32-or-more-bit type to hold products,
    you could use int16_t (exactly 16) and int_least32_t (32 or more).
    Various people have written <stdint.h> fill-ins for pre-C99
    compilers, and you may be able to find one you can borrow. If not,
    for what you need it's straightforward to cobble something together
    using the values in <limits.h>, e.g.

    #include <limits.h>
    #if INT_MAX == 0x7fff
    typedef int int16_t;
    #elif (INT_MAX >> 16) == 0x7fff /* avoid large constants */
    typedef int int32_t;
    Eric Sosman, Jul 9, 2013
  9. James Harris

    James Harris Guest

    Here's what I came up with derived from Eric's advice (errors my own). The lower piece of code is to represent manageable parts of a 64-bit increasingcounter called the TSC. Don't worry about the details but I've included them here as I know it can be frustrating when people don't post enough info.

    /* Define double-width integer types */
    #if LONG_MAX / INT_MAX / INT_MAX == 2
    typedef long intd_t;
    typedef unsigned long uintd_t;
    #elif LLONG_MAX / INT_MAX / INT_MAX == 2
    typedef long long intd_t;
    typedef unsigned long long uintd_t;
    #error "No viable candidate for intd_t and uintd_t"

    /* Define types for the Time Stamp Counter */
    #if INT_MAX == 32767
    typedef unsigned int tsc_low_t; /* low 16 bits */
    typedef uintd_t tsc_main_t; /* low 32 bits */
    #elif INT_MAX == 2147483647
    typedef unsigned int tsc_low_t; /* low 32 bits */
    typedef uintd_t tsc_main_t; /* full 64 bits */
    #elif INT_MAX == 9223372036854775807
    typedef unsigned int tsc_low_t; /* full 64 bits */
    typedef unsigned int tsc_main_t; /* full 64 bits */
    #error "Failed to define types for the TSC"

    Your use of UINT_MAX may be clearer than my use of INT_MAX. I might change my code to use UINT_MAX instead. Given that the preprocessor accepts shiftsit might be possible to code those constants as (1 << shift_amount) - 1 but I would be wary of overflow.
    Agreed. At least for the TSC the #error clause should suffice if it will report the error and abort the compilation. I have no need to support unusualinteger widths and only need them to be powers of 2 in the range 16 to 64.Given that, I suppose I could change the double-width integer definition but it seems good as it is.
    I will need some other integer types but in the context of the initial postin this thread they need to be exact widths as they will interface with assembly code.

    There is no stdint.h included with my 16-bit compiler so I cannot use thosetypes (unless I define them myself - which I might have to do for other code).

    James Harris, Jul 9, 2013
  10. James Harris

    James Kuyper Guest

    I think my use of hexadecimal constants instead of decimal ones makes it
    much easier to be sure that the values of those constants are correct
    (though there's still the possibility that I miscounted the 'F's).
    To avoid the overflow problem, use 1ULL rather than 1, but that only
    works for C99 or later.
    James Kuyper, Jul 9, 2013
  11. The real answer is to write the code in such a way that it doesn't rely
    on any particular integer width, other than an integer being able to index
    an arbitrary array. Whilst this is always possible, there are a few problems
    where it's too fiddly and inefficient, for example if you need all the bits
    of c = a * b.

    In practice CHAR_BIT will always be 8, short will be 8, 16 or 32 bits, long
    will be 16, 32 or 64 bits, and long long will be 32, 64 or 128 bits. On
    a 32 bit system, short will be 16 and long will be 32 or 64. On a 64 bit
    system short will be 16 or 32, long will be 32 or 64, and long long will
    be 64 or 128. So there aren't that many possibilities, if you can accept
    the compile breaking on a deliberately perverse or odd system.
    Malcolm McLean, Jul 9, 2013
  12. James Harris

    Jorgen Grahn Guest

    +1. The question "what are you really trying to do?" seems
    appropriate. Some requirement hides behind the "I want an portable
    integer exactly twice as wide as int" statement, but we can only guess
    what it is. "int * int without overflow" was my guess, too.

    Jorgen Grahn, Jul 9, 2013
  13. [...]

    Most systems that are generally thought of as "64-bit" have 32-bit int
    (including the x86_64 system I'm typing this on).

    Pointer size tends to be a more reliable indicator of "bitness", though
    8-bit systems almost always have pointers bigger than 8 bits. Register
    size is usually a better indication, but that tends not to be accessible
    from C.

    Part of the problem is that there's no good definition of what a 32-bit
    or 64-bit system is.
    Keith Thompson, Jul 9, 2013
  14. Doug Gwyn, a member of the ISO C standard committee, created some C99
    (then C9x) porting supplements, including an implementation of
    <stdint.h> for pre-C99 compilers. It's public domain, so you can use it
    any way you like.

    Keith Thompson, Jul 9, 2013
  15. Exceptions to CHAR_BIT==8 are admittedly rare; compilers for DSPs
    (digital signal processors) are the most commonly cited examples.

    short and int are both guaranteed to be at least 16 bits, long is
    guaranteed to be at least 32 bits, and long long is guaranteed to be
    at least 64 bits. The sizes are guaranteed to be non-decreasing
    across the sequence signed char, short, int, long, long long.
    (Actually the *ranges* are guaranteed to be non-decreasing, which
    is the same thing in the absence of padding bits, which are rare.)

    In practice, I've only seen short bigger than 16 bits on Cray
    systems, and I've never seen long long with a size other than exactly
    64 bits. (gcc supports 128-bit integers on some platforms, but it
    still makes both long long and uintmax_t 64 bits.) There's some
    pressure to provide predefined types of size 8, 16, 32, and 64 bits;
    making short 32 bits with CHAR_BIT==8 means there is no 16-bit
    integer type, which is inconvenient. (That could be worked around
    with extended integer types, but those aren't commonly supported.)
    Keith Thompson, Jul 10, 2013
  16. You can't rely on that. Sometimes int is 8 bits, and long 16. Presumably short would be 8 bits, but it's quite useless type on such a system.
    Malcolm McLean, Jul 10, 2013
  17. James Harris

    Noob Guest

    By definition,

    you can rely on INT_MAX and -INT_MIN being *AT LEAST* 0x7fff
    you can rely on LONG_MAX and -LONG_MIN being *AT LEAST* 0x7fffffff
    you can rely on LLONG_MAX and -LLONG_MIN being *AT LEAST* 0x7fffffffffffffff
    (Not on an implementation of C.)
    Please explain how you'd stuff 65535 values within 8 bits.
    Noob, Jul 10, 2013
  18. James Harris

    James Kuyper Guest

    It's quite true that the C standard guarantees what he said. The systems
    you describe don't conform to that standard.
    Once you expand the topic to include things that don't conform to the C
    standard, the topic becomes too fuzzy to be worth talking about. Some
    things that don't conform don't even have "int", they use "integer"
    instead. Some things that don't conform don't even have data types: for
    instance, your typical baseball is something doesn't conform to the C
    James Kuyper, Jul 10, 2013
  19. I'd disagree. A language can be nearly conforming, and still what anyone
    would call C. Obviously as it gets steadily further from standard C
    and from K and R C, the issue will arise, "is it still really C or is
    it now effectively another language?". But compilers for tiny embedded
    system that think it's more important that int fit in a register than
    that it hold values of up 32767 aren't anywhere near that boundary.
    Malcolm McLean, Jul 10, 2013
  20. Yes, you can rely on that if you're programming in C.

    A compiler with 8-bit int or 16-bit long is not a C compiler
    (though it may be a compiler for a language resembling C).
    Keith Thompson, Jul 10, 2013
    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.