Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Introducing the "struct Hack"

Last updated Jan 1, 2003.

Another core language feature that was introduced in C99 is called the "struct hack". What is the struct hack and what’s it good for? Here are the answers.

What’s an Incomplete Type?

The struct hack has been popular with C programmers for a long time. In the POSIX world in particular, it’s been widely supported although it was standardized only in C99. In essence, the struct hack allows the last member (and only the last member) of a struct to be an incomplete type. Here’s an example:

struct S {
 int sz; 
 double d[];
};

The last data member of struct S is an array of double whose size isn’t specified. This member is said to have an "incomplete type" in standardese, because an array’s dimension(s) are considered part of its type in both C and C++. Consequently, the entire struct S has an incomplete type. In C++98, this is illegal. If you compile this struct declaration in strict C++98 mode, your compiler will issue a diagnostic message. In C99, a declaration of this type is perfectly valid. This is another annoying incompatibility between ISO C++ and ISO C. To the best of my knowledge, the C++ standards committee is now working on various C99 compatibility issues but this isn’t one of them. In practice however, all leading compilers, including Microsoft’s Visual Studio 2005, support this feature. Some of them require special compilation flags, though.

Implementation

How is this feature used? Remember that the main purpose for using the struct hack is deferring the array’s size computation to runtime. Let’s look at the following example:

struct Str {
 int sz; 
 char s[];
};
Str *p1, *p2;

p1= malloc(sizeof (Str) + (sizeof (char) * 20));
//p1 now behaves as if it had been declared as:
struct Str
{
 int sz;
 char s[20]
};

p1= malloc(sizeof (Str) + (sizeof (char) * 80));
//p2 now behaves as if it had been declared as:
struct Str
{
 int sz;
 char s[80]
};

free(p1);
free(p2);

The struct hack allows you to complete the specification of a given struct at runtime. This is the closest you get to dynamic type specification in C. How does it work? C99 guarantees that there shall be no padding bytes before the last member of the struct, or, if there are padding bytes, they shall be included in sizeof (struct Str). Thus, while Str is an incomplete type, the expression sizeof (Str) is valid and its result should be equivalent to:

offsetoff(Str, d);

Due to its dynamic nature, the struct hack imposes a few restrictions on its usage. While it’s possible to create an object of type Str on the stack, there is no standard and portable way to allocate the incomplete array thereof on the stack. You will have to allocate it on the heap:

Str s; //OK, but not ideal
s->s=malloc(sizeof(char)*80));
s->sz=80;

Therefore, it’s best to allocate the entire struct object on the heap. C++ programmers will face another problem: pointers in C++ are strongly typed, unlike pointers in C. Therefore, you must use an explicit cast expression to convert malloc()’s return type (which is void*) to S *. Which cast operator should this be? static_cast should work but as you already know I can’t stand its syntax nor the potential confusion that the plethora of C++ cast operators cause. Good old C-style cast will do, with far fewer keystrokes:

//C++ pundits would prefer:
//Str *p = static_cast<Str*>(malloc (sizeof (Str) + (sizeof (char) * n)));
Str *p = (Str*) malloc (sizeof (Str) + (sizeof (char) * n));

"Can I replace malloc() with new[]?" is probably the next question. Yes, you can. Notice however that you should use operator new instead of plain new []. The main difference between the two is that new[] takes an explicit type, and returns the address of the first element of an array of that type. By contrast, operator new takes only the number of bytes as its argument and returns void*:

Str *p = (Str*) operator new (sizeof (Str) + (sizeof (char) *80));

Remember that you must use delete[] instead of free() to release the allocated memory in this case.

What’s it good for?

The struct hack can be useful in various occasions, for example, in an application that accesses a relational database with variable size records. A good example is a record that contains a string (say, an employee’s name). Using the struct hack, you can create a single struct template that can fit any record within the same table. All you need to do is detect the actual length of the string stored inside a given record, and allocate a tailor-made struct for it.

Another useful application of the struct hack is witnessed in the multimedia world. Many media file formats are based on a common theme: the file contains a fixed size header followed by an arbitrary stream of bytes. For example, an MP3 file consists of a header with a fixed size that contains the bitrate, the number of channels, the artists’ name etc. and the number of data bytes:

struct MP3_HEADER
{
int bits_per_second;
int channels;
int CRC;
long long bytes;
};

Using the struct hack, you can create a single struct that can accommodate every MP3 file:

struct MP3_FILE
{
int bits_per_second;
int channels;
int CRC;
long long bytes;
unsigned char bytestream[];
};

Indeed, if you’re a C++ programmer, you will find it less useful than a C programmer. Instead of resorting to such hacks, you can use safer and higher-level vector object. However, when binary compatibility is necessary or when you want to implement a simple serialization mechanism, the struct hack can still be useful.