The C language does not define any specific layout for the storage of data items relative to one another. The layout for storage of structure elements, or unions within a structure or union, is defined by the implementation.
Some processors require that data longer than one byte be aligned to two-byte or four-byte boundaries. Other processors, such as the 80x86 family, do not have such a restriction. However, the 80x86 processors work more efficiently with aligned data.
The following example illustrates how alignment can affect your program. In the example, a structure is cast to type long because the programmer knew the order in which a particular implementation stored data.
/* Nonportable code */
struct time
{
char hour; /* 0 < hour < 24 — fits in a char */
char minute; /* 0 < minute < 60 — fits in a char */
char second; /* 0 < second < 60 — fits in a char */
};
.
.
.
struct time now, alarm_time;
.
.
.
if ( *(long *)&now >= *(long *)&alarm_time )
{
/* sound an alarm */
}
The preceding code makes these nonportable assumptions:
The data for hour will be stored in a higher order position than minute or second. Because C does not guarantee storage order or alignment of structures or unions, the code may not be portable to other machines.
Three variables of type char will be shorter than or the same length as a variable of type long. Thus, the code is not portable according to the rules governing the size of basic types, as described in “Size of Basic Types”.
If either of these assumptions proves false, the comparison (if statement) is invalid.
Summary: You can write code that makes no assumptions about storage order.
To make the program in the preceding example portable, you can break the comparison between the two long integers into a component-by-component comparison. This technique is illustrated in the following example:
/* Portable code */
struct time
{
char hour; /* 0 < hour < 24 — fits in a char */
char minute; /* 0 < minute < 60 — fits in a char */
char second; /* 0 < second < 60 — fits in a char */
};
.
.
.
struct time now, alarm_time;
.
.
.
if ( time_cmp( now, alarm_time ) >= 0 )
{
/* sound an alarm */
}
.
.
.
int time_cmp( struct time t1, struct time t2 )
{
if( t1.hour != t2.hour )
return( t2.hour - t1.hour );
if( t1.minute != t2.minute )
return( t2.minute - t1.minute );
return( t2.second - t1.second );
}
Programmers use unions most often for two purposes: to store data whose exact type is not known until run time or to access the same data in different ways.
Unions falling into the second category are usually not portable. For example, the following union is not portable:
union tag_u
{
char bytes_in_long[4];
long a_long;
};
The intent of the preceding union is to access the individual bytes of a variable of type long. However, the union may not work as intended when ported to other computers because
It relies on a constant size for type long.
It may assume byte ordering within a variable of type long. (Byte ordering is described in detail in “Byte Order in a Word”.)
The first problem can be addressed by coding the union as follows:
union tag_u
{
char bytes_in_long[sizeof( long ) / sizeof( char )];
long a_long;
};
Note the use of the sizeof operator to determine the size of a data type.