Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use C99 standard types for maximum portability AND efficiency across most platforms?

First, here is what I understand and think what is true for the question.

  1. Use fast data types for single variables like counters or for loop indexes. For example:

    #define LOOP_COUNT (100U)
    
    uint_fast8_t index;
    for(index = 0; index < LOOP_COUNT; index++){
        /* Do something */
    }
    

    I suppose the most suitable type in here is uint_fast8_t since index can never exceed 255 and this will be the fastest implementation for all platforms. If I used unsigned int instead, it will be fastest in >=16 bits platforms but will be slower in <16 bits platforms as int is 16 bits minimum by standard. Also, if I used uint8_t it will be slower on >8 bits platforms as the compiler adds an AND 0xFF instruction to check overflow for each increment (my ARM7 compiler does that even on full speed optimization). size_t is also not an option since it can be bigger than the native integer size.

    Bad side (?) of this if an overflow is expected for 8 bits, it is not gonna happen. Programmer should check for overflow manually (as he/she should IMHO), which may result in buggy code if forgotten. Also, compiler (and even PC-Lint to my surprise) will not give any warning/issue if LOOP_COUNT "accidentally" set to a value bigger than 255 on >8 bits platforms, but the warning will be generated on an 8 bits platform, which will reduce portability and introduce bugs, but this can be avoided with #if checks.

  2. Use least data types as much as possible if memory usage is of concern like in arrays or structures. For example:

    uint_least8_t array[100];
    

    It is the most portable and efficient way to declare arrays if memory usage is of concern. This type will give a byte array if byte access is possible on the platform, and give the smallest accessible width integer array otherwise. Also, least types can be used in structures if we have arrays of the structure.

    Least type can also suffer the problems fast types do, as width of variables can be changed on different platforms for both cases.

  3. Avoid fixed width data types as much as possible as they may not even exist on some platforms, except hardware register access, communication protocol mapping, etc. where we need to know the exact bits of the variable. For example:

    typedef struct {
        uint8_t  flags;
        uint8_t  length;
        uint8_t  data[100];
        uint16_t crc;
    } __attribute__((packed)) package_t;
    

    Usually __attribute__((packed)) (or something similar) should be used to ensure no padding will be inserted for these cases, as this can be a problem by itself.

Now, if my understanding is true, I think least data types are more likely to be used in arrays or structures, fast data types are more likely to be used for single variables and fixed data types are unlikely to be used in order to achieve maximum portability and efficiency. But typing "fast" and "least" every time is not encouraging. So, I think of a type set as follows:

    typedef [u]intN_t       os_[u|s]exactN_t;
    typedef [u]int_fastN_t  os_[u|s]N_t;
    /* I couldn't come up with a better name */
    typedef [u]int_leastN_t os_[u|s]minN_t;
    /* These may change */
    typedef uint_least8_t   os_byte_t;
    typedef uint_least16_t  os_word_t;
    /* ... */
  • First and the important question is, is my understanding true?
  • What would be the most portable AND efficient way to use C99 standard types and how would you declare them if it is not true?
  • Does my type set makes sense or is it likely to produce buggy code?

Also, I would be pleased to know how and where you use C99 standard types.

like image 560
obareey Avatar asked Apr 03 '13 17:04

obareey


People also ask

Why use C99?

A major feature of C99 is its numerics support, and in particular its support for access to the features of IEEE 754-1985 (also known as IEC 60559) floating-point hardware present in the vast majority of modern processors (defined in "Annex F IEC 60559 floating-point arithmetic").

Is C99 Portable?

The ISO C99 standard introduced portable data types through a header file named stdint. h. This header file is included with the compiler and creates data types of a fixed width. A fixed width data type ensures that an 8 bit variable is 8 bits, a 16 bit variable is 16 bits and so on and so forth.

Which is portable integer type in C?

int32_t: signed 32-bit. uint32_t: unsigned 32-bit. int64_t: signed 64-bit. uint64_t: unsigned 64-bit.


1 Answers

Writing highly portable code is hard. Writing highly portable code that is optimal and works correctly is even harder.

For the majority of time, if it is feasible, I would suggest using basic types such as int, char, etc, rather than uint8_t or uint8_fast_t. The types int and char is guaranteed to exist. There is no doubt about that. Of course, SOMETIMES we need a specific behaviour from the code, and that will require a specific type - but that code will most likely break if the system doesn't support that exact type.

For your first example, it is extremely unlikely that you'll get better performance than using int, unless your code is designed to (also) run on 8-bit processors. On a 16-, 32- or 64-bit processor, the native size will be the fastest for loops (unsigned being slightly better on 64-bit machines, since it doesn't require sign extension).

In your second example, it really only matters if the array is large enough to warrant the space-saving over using either char or int or short or whatever makes sense for the content. On modern machines (including many embedded platforms and even when usign the stack) 400 bytes isn't really that much.

For your third example, obviously, for protocols, you will need to use types that match the protocol definition exactly, or things will go wrong. On platforms that doesn't support the correct type, this will have to be solved in some platform specific way - how you go about it will depend on exactly what the platform actually supports.

So, to answer your concrete questions:

  • Seems like you understand the overall concept. But you also seem to be trying to push it further than it needs to be pushed, if that makes sense.
  • Not applicable.
  • Excessive use of "special type" variables is likely to:

    1. slow things down,
    2. potentially cause bugs, especially if it's not used consistently.

Remember also that performance is often a case of 90% of the time is taken by 10% of the code. Understanding where (under normal usage) your code spends its time is critical. Of course, when porting code to different systems and on different architectures, you may find that the performance bottleneck moves, based on the relationship between processor speed, size of caches and memory speed. A system with high processor speed, but (realtively) small caches can sometimes perform worse than a similar system with lower clock-speed and bigger caches, just as one example.

like image 172
Mats Petersson Avatar answered Oct 21 '22 02:10

Mats Petersson