Type punning


In computer science, type punning is a common term for any programming technique that subverts or circumvents the type system of a programming language in order to achieve an effect that would be difficult or impossible to achieve within the bounds of the formal language.
In C and C++, constructs such as pointer type conversion and union — C++ adds reference type conversion and reinterpret_cast to this list — are provided in order to permit many kinds of type punning, although some kinds are not actually supported by the standard language.
In the Pascal programming language, the use of records with variants may be used to treat a particular data type in more than one manner, or in a manner not normally permitted.

Sockets example

One classic example of type punning is found in the Berkeley sockets interface. The function to bind an opened but uninitialized socket to an IP address is declared as follows:

int bind;

The bind function is usually called as follows:

struct sockaddr_in sa = ;
int sockfd =...;
sa.sin_family = AF_INET;
sa.sin_port = htons;
bind;

The Berkeley sockets library fundamentally relies on the fact that in C, a pointer to struct sockaddr_in is freely convertible to a pointer to struct sockaddr; and, in addition, that the two structure types share the same memory layout. Therefore, a reference to the structure field my_addr->sin_family will actually refer to the field sa.sin_family. In other words, the sockets library uses type punning to implement a rudimentary form of polymorphism or inheritance.
Often seen in the programming world is the use of "padded" data structures to allow for the storage of different kinds of values in what is effectively the same storage space. This is often seen when two structures are used in mutual exclusivity for optimization.

Floating-point example

Not all examples of type punning involve structures, as the previous example did. Suppose we want to determine whether a floating-point number is negative. We could write:

bool is_negative

However, supposing that floating-point comparisons are expensive, and also supposing that float is represented according to the IEEE floating-point standard, and integers are 32 bits wide, we could engage in type punning to extract the sign bit of the floating-point number using only integer operations:

bool is_negative

Note that the behaviour will not be exactly the same: in the special case of x being negative zero, the first implementation yields false while the second yields true.
This kind of type punning is more dangerous than most. Whereas the former example relied only on guarantees made by the C programming language about structure layout and pointer convertibility, the latter example relies on assumptions about a particular system's hardware. Some situations, such as time-critical code that the compiler otherwise fails to optimize, may require dangerous code. In these cases, documenting all such assumptions in comments, and introducing static assertions to verify portability expectations, helps to keep the code maintainable.
For a practical example popularized by Quake III, see fast inverse square root.
In addition to the assumption about bit-representation of floating-point numbers, the previous floating-point type-punning example also violates the C language's constraints on how objects are accessed:ISO/IEC 9899:1999 s6.5/7 the declared type of x is float but it is read through an expression of type unsigned int. On many common platforms, this use of pointer punning can create problems if different pointers are aligned in machine-specific ways. Furthermore, pointers of different sizes can alias accesses to the same memory, causing problems that are unchecked by the compiler.

Use of union

It is a common mistake to attempt to fix type-punning by the use of a union.

bool is_negative

Accessing my_union.ui after initializing the other member, my_union.d, is still a form of type-punning in C and the result is unspecified behavior.
The language of § 6.5/7 can be misread to imply that reading alternative union members is permissible. However, the text is "An object shall have its stored value accessed only by…". It is a limiting expression, not a statement that all possible union members may be accessed regardless of which was last stored. So, the use of the union avoids none of the issues with simply punning a pointer directly.
Some compilers like GCC support such non-standard constructs as a language extension.
For another example of type punning, see Stride of an array.

Pascal

A variant record permits treating a data type as multiple kinds of data depending on which variant is being referenced. In the following example, integer is presumed to be 16 bit, while longint and real are presumed to be 32, while character is presumed to be 8 bit:

type
VariantRecord = record
case RecType : LongInt of
1: ;
2: ;
3: ;
4: ;
end;
var
V : VariantRecord;
K : Integer;
LA : LongInt;
RA : Real;
Ch : Character;
V.I := 1;
Ch := V.C;
V.R := 8.3;
LA := V.L;

In Pascal, copying a real to an integer converts it to the truncated value. This method would translate the binary value of the floating-point number into whatever it is as a long integer, which will not be the same and may be incompatible with the long integer value on some systems.
These examples could be used to create strange conversions, although, in some cases, there may be legitimate uses for these types of constructs, such as for determining locations of particular pieces of data. In the following example a pointer and a longint are both presumed to be 32 bit:

type
PA = ^Arec;
Arec = record
case RT : LongInt of
1: ;
2: ;
end;
var
PP : PA;
K : LongInt;
New;
PP^.P := PP;
WriteLn;

Where "new" is the standard routine in Pascal for allocating memory for a pointer, and "hex" is presumably a routine to print the hexadecimal string describing the value of an integer. This would allow the display of the address of a pointer, something which is not normally permitted. Assigning a value to an integer variant of a pointer would allow examining or writing to any location in system memory:

PP^.L := 0;
PP := PP^.P;
K := PP^.L;
WriteLn;

This construct may cause a program check or protection violation if address 0 is protected against reading on the machine the program is running upon or the operating system it is running under.
The reinterpret cast technique from C/C++ also works in Pascal. This can be useful, when eg. reading dwords from a byte stream, and we want to treat them as float. Here is a working example, where we reinterpret-cast a dword to a float:

type
pReal = ^Real;
var
DW : DWord;
F : Real;
F := pReal^;

C#

In C#, type punning is a little harder to achieve because of the type system, but can be done nonetheless, using pointers or struct unions.

Pointers

C# only allows pointers to so-called native types, i.e. any primitive type, enum, array or struct that is composed only of other native types. Note that pointers are only allowed in code blocks marked 'unsafe'.

float pi = 3.14159;
uint piAsRawData = *π

Struct unions

Struct unions are allowed without any notion of 'unsafe' code, but they do require the definition of a new type.


struct FloatAndUIntUnion
//...
FloatAndUIntUnion union;
union.DataAsFloat = 3.14159;
uint piAsRawData = union.DataAsUInt;

Raw CIL code

Raw CIL can be used instead of C#, because it doesn't have most of the type limitations. This allows one to, for example, combine two enum values of a generic type:

TEnum a =...;
TEnum b =...;
TEnum combined = a | b; // illegal

This can be circumvented by the following CIL code:

.method public static hidebysig
!!TEnum CombineEnums cil managed

The cpblk CIL opcode allows for some other tricks, such as converting a struct to a byte array:

.method public static hidebysig
uint8 ToByteArray cil managed