Portable Fixed Width Integer types in C99

Portable Fixed Width Integer types in C99

If you are an embedded software developer, then believe me this article will help you. Before the C99, the C standard only introduced built-in types with no defined size. This ambiguity is intentional in the original  C standard to give the compiler vendors more flexibility. But sometimes it creates a problem and reduces portability.

According to the C standard, the size of the long must be at least 32 bits and the size of the int and short must be at least 16 bits but the size of the short must be no longer than the int.

//C built-in types arranged in size (bytes)

sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long) (only C99)

 

You can resolve the problem by creating a header file, where you can create a fixed-width integer type using the typedef and original built-in data type. In the project, you need to include this created header file in all source files (.c)

But the problem with this handmade header file is that it can not be universal. There are two important reasons, first, it uses some made-up name, which is not standardized in the same sense as the built-in type is. And second definition is only correct for the specific processor and specific compiler.

 

So to resolve this problem the C standard introduces a new <stdint.h> header file in the C99 standard. For the embedded software developer <stdint.h> header file is the most valuable feature introduced in the C99 standard.

As you can see, the file uses the typedefs to define the fixed width integer types. According to the updated standard, this required set of typedefs (along with some others) is to be defined by compiler vendors and included in the new header file stdint.h . Let’s see some newly defined fixed-width integer types,

Size Signed Unsigned
8-bit: int8_t uint8_t
16-bit: int16_t uint16_t
32-bit: int32_t uint32_t
64-bit: int64_t uint64_t

 

#include <stdio.h>
#include <stdint.h>

int main()
{
    //signed
    printf("sizeof(int8_t)   = %zu\n", sizeof(int8_t));
    printf("sizeof(int16_t)  = %zu\n", sizeof(int16_t));
    printf("sizeof(int32_t)) = %zu\n", sizeof(int32_t));
    printf("sizeof(int64_t)  = %zu\n", sizeof(int64_t));

    //unsigned
    printf("sizeof(uint8_t)  = %zu\n", sizeof(uint8_t));
    printf("sizeof(uint16_t) = %zu\n", sizeof(uint16_t));
    printf("sizeof(uint32_t) = %zu\n", sizeof(uint32_t));
    printf("sizeof(uint64_t) = %zu\n", sizeof(uint64_t));

    return 0;
}

Output:

Fixed width integer types

 

Let’s discuss some important concepts related to the integer. I have already written an article on signed vs unsigned integer. If you want you can read this article.

Click Here

 

Scenario 1:

When you will compile the below code on a 32-bit machine, the program will work fine and you will get the expected result.

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint16_t a = 40000;
    uint16_t b = 50000;

    uint32_t c = a + b;

    printf("%u\n",c);
    return 0;
}

Output:

fixed width integer types

 

 

Now running the same code on a 16-bit machine where standard int is 16-bit wide.

integer promotion 16-bit machine and overflow

 

Oh my God, you are not getting the actual result that you assumed. The basic reason behind this output is overflow. The C automatically promotes any smaller size integer to the built-in type int or unsigned int before performing any computation.

So when you ran the same code on a 32-bit machine, the integer promotion was 32 bits because the int size is 32-bits. But for 16 bits there is no real promotion because type int is only 16 bits wide.

calculation integer promotion

 

Now we know the problem but how we can resolve this problem?

The solution is very simple we only need to enforce the promotion to 32-bit precision of at least one operand ‘a’ or ‘b’. If one of the operands 32 bit wide others will automatically be promoted to 32-bits wide and the whole operation performed at 32 bits.

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint16_t a = 40000;
    uint16_t b = 50000;

    uint32_t c = (uint32_t)a + b;

    printf("%u\n",c);
    return 0;
}

 

integer promotion 16 bits

 

Note: You can typecast explicitly both operand ‘a’ and ‘b’.

 

Scenario 2:

When we mix signed and unsigned numbers together it creates a problem if we will not carefully handle it. Let’s see a program and compile and run it on a 32-bits machine where the size of int is 32 bits.

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint16_t a = 50;

    int32_t b = 10 - a;

    printf("%d\n",b);
    
    return 0;
}

signed And Unsigned number

 

You can see that we are getting the expected value on a 32-bits machine. But the problem arises when you will run the same code on a machine where int is 16-bits wide.

fixed width integer types

 

In the above case, you have mixed signed and unsigned operands together so implicit conversion will occur. Both operands are promoted to unsigned int and the result is unsigned int. The result will convert signed 32 because the left signed operand is 32 bits wide.

When this code runs on a 16-bit machine then the issue occurs because the unsigned int is 16-bits wide here. So if we assign the 2’s complement value of unsigned int to variable b (which is 32-bits wide), it only fills up the lower half of the bytes of b. Because the value is unsigned, and it is not signed extended to 32 bits and is interpreted as a big positive value.

We can avoid this non-portable issue to avoid mixing signed and unsigned operands by making the unsigned operands to signed explicitly.

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint16_t a = 50;

    int32_t b = 10 - (int16_t)a;

    printf("%d\n",b);
    
    return 0;
}

 

 

 

Scenario 3:

Another issue occurs when you mixed signed and unsigned integers in comparison statements. Let’s see the below example where only else part will execute.

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint32_t a = 100;

    if (a > -1)
    {
        printf(" a > -1");
    }
    else
    {
        printf(" a < -1");
    }

    return 0;
}

Output:

a < -1

 

We can also resolve this issue by explicit typecasting signed int.

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint32_t a = 100;

    if ((int32_t)a > -1)
    {
        printf(" a > -1");
    }
    else
    {
        printf(" a < -1");
    }

    return 0;
}

Output:

a > -1

 

Recommended Post

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *