The Concept Of System Endianness And Structure Padding Explained Through C Examples

December 30, 2013 | By
| 3 Replies More

In this article, we will discuss the concept of endianness and structure padding through C examples.

Here are the details of testing environment :

  • OS – Ubuntu 13.04
  • Shell – Bash 4.2.45
  • Compiler - gcc (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3

Concept of endianness in C program

System Endianness

Have you ever given a thought on how multi-byte data-types (like integer, float, etc) are stored in memory? Well, if you think that an integer value like 0x1234 (in Hex) is stored in memory as 12 34 then you are only partially correct. You have to understand the concept of endianness to get a better idea of how values are stored in memory.

In this section we will discuss the little-endian and big-endian storage formats.

Little Endian

In this format, the last byte of a multi-byte data-type value is stored first and the first byte is stored last. For example, the value 0x1234 is stored as :

34 12

Similarly, the value 0x12345678 is stored as :

78 56 34 12

on little-endian machines.

Big Endian

In this format, the initial byte of a multi-byte data-type value is stored first and the final byte is stored last. For example, the value 0x1234 is stored as :

12 34

Similarly, the value 0x12345678 is stored as :

12 34 56 78

on big-endian machines.

If you observe, you'll see that these two storage formats are exactly opposite of each other.

Why is endianness important?

Machines store values according to their endianness. For example, IBM and DEC machines are little endian while Sun and Motorola machines are big-endian. Now, suppose you send a file containing integer values from a big-endian system to a little-endian system, you'll definitely observe reversed values when you try to read them on the little-endian system.

Due to the existence of both types of systems, the network data format is kept fixed, and is always transferred in big-endian format. So, as a programmer you have to make sure that the data is read accordingly.

How to determine endianness of your machine?

Here is a program through which you can easily determine endianness of your machine :

#include <stdio.h>

union u
{
short a;
char b;
}un;

int main(void)
{
un.a = 0xabcd;
if(un.b == 0xab)
printf("\n The system is big-endian\n");
else
printf("\n The system is little-endian\n");

return 0;
}

So in this program, I have declared a union of a short integer and a character. In the main function, a 2 byte value is assigned to the short integer and then the first byte is accessed through the character 'b' (both short and char variables are part of union, and hence point to same value). If the first byte is 0xab then the system is big-endian, else little-endian.

When I executed this program on my machine, the output was little endian :

$ ./endian

The system is little-endian

So you can see that how important it is for programmers to understand the concept of system endianness.

Structure Padding

Before we jump on to the explanation, lets consider an example :

#include<stdio.h>

struct st
{
char c;
int a;
}obj;

int main(void)
{
printf("\n Size of structure is [%lu]\n", sizeof (struct st));

return 0;
}

What will be the size of the structure according to you? Do you agree that it will be sizeof(char) + sizeof(int) i.e., 1 + 4 = 5 bytes? Well, If your answer is yes, I am afraid that you are wrong.

Here is the output when the program was executed in my machine :

$ ./padding

Size of structure is [8]

So you can see that the actual size is 8 bytes. But, you may be wondering where the 3 extra bytes came from? Lets come back to this later.

We talked about data storage formats in the last section. It is also worth understanding that every data-type in C/C++ have its own alignment requirements. For example, a char value is byte-aligned, which means that it can be stored at any address. A short value is 2 byte aligned, which means that it is always stored at an address which is a multiple of two. On the similar lines, integer and double values are 4 byte and 8 byte aligned respectively.

Now, one might ask, how alignment helps? See, architecture of a computer processor is such that either it can read 4 bytes or 8 bytes in one go (I am talking about 32 bit and 64 bit processors here). So, if we talk about a 32 bit processor, it will be easy for it to read information packaged into 4 bytes. If an integer doesn't start at an address which is multiple of 4, it will cause performance issues as processor will have to do multiple cycles to read the integer value.

Coming back to the example we discussed earlier. The reason those 3 extra bytes were added because compiler wanted to align integer to a 4 byte memory. Lets modify the same program to print the addresses :

#include<stdio.h>

struct st
{
char c;
int a;
}obj;

int main(void)
{
printf("\n Address of 'c' is [%lu] \n", (long unsigned) &obj.c);
printf("\n Address of 'a' is [%lu] \n", (long unsigned) &obj.a);
printf("\n Size of structure is [%lu]\n", sizeof (struct st));

return 0;
}

When the program was executed, the following output was produced :

$ ./padding

Address of 'c' is [6295620]

Address of 'a' is [6295624]

Size of structure is [8]

So you can see that if the address 6295621 would have been allocated to integer 'a', it would have violated the alignment rules and the processor would not have been able to fetch the value in one go. Hence, compiler added 3 padding bytes after the address 6295620 and then allocated memory for integer 'a' on address 6295624, which is a multiple of 4.

So, in a nutshell, you can decode padding in any structure by keeping in mind the processor type (32 bit or 64 bit) and applying the alignment rules for each data-type.

Filed Under : PROGRAMMING

Free Linux Ebook to Download

Comments (3)

Trackback URL | Comments RSS Feed

  1. Thiago Macieira says:

    Please note that your first example, the one you used to determine the endianness, runs into an undefined behaviour unless you force the compilation to be C++11.

    When you do:
    un.a = 0xabcd;
    if(un.b == 0xab)

    You're writing to one member of a union and reading the other. The C99 and C++98 standard allows compilers to think like this: "un.a was never read from again, so I don't need to actually write to it". That means you're reading uninitialised data in un.b. One compiler that is known to do exactly this optimisation is one from Sun Studio.

    I don't remember if C11 copied the C++11 change.

    Using a cast from int to short, short to int would also be wrong: strict aliasing violation.

    The correct way to do this under C99 would be to use memcpy or a char*. Chars are allowed to alias.

  2. Pierre Louvannet says:

    Great article!

    I just have one question about the following sentence. you wrote: "If an integer doesn’t start at an address which is multiple of 4, it will cause performance issues as processor will have to do multiple cycles to read the integer value."

    Could you explai why, please?
    I'm curious to know why it would be faster for the CPU to read the 0x00..04 adress than the 0x00..05, for exemple.

Leave a Reply

Commenting Policy:
Promotion of your products ? Comment gets deleted.
All comments are subject to moderation.