Click here to Skip to main content
15,888,113 members
Articles / Programming Languages / C
Tip/Trick

C Struct Hack

Rate me:
Please Sign up or sign in to vote.
3.15/5 (9 votes)
2 Oct 2016CPOL1 min read 32.5K   8   14
This tip introduces struct hack and compares it with several implementation alternatives.

This tip introduces struct hack and compares it with several implementation alternatives. A typical C struct hack is a struct whose last element is an array of size one.

C++
struct Foo
{
  //..
  size_t size;
  // Better to use char array to be more portable, as discussed in comments.
  int data[1];
};

const size_t SIZE = 100;
Foo *p = (Foo*) malloc(sizeof(Foo) + sizeof(int) * (SIZE - 1));
p->size = SIZE;
for (int i = 0; i < p->size; ++i) p->data[i] = i;

The trick is to allocate more memory than sizeof (Foo), and make a Foo* point to it. The memory allocated is filled with a Foo object at the beginning, followed by an array of “dynamic” number of integers. You just reference the out-of-bounds part of the array such that you stay inside the memory actually allocated. That is, you can visit p->data[0] as well as p->data[1] and so on up until you hit the end of the memory you allocated. That said, you implement a flexible array member in C.

Why Not Use a Pointer?

C++
struct Foo
{
  // ..
  size_t size;
  // data = (int*)malloc(sizeof(int) * 10);
  int *data;
};

The advantage of using an array is that you don’t have to allocate the memory elsewhere and make the pointer point to that. Thus there is no extra memory management. Furthermore, accesses to the memory will hit the memory cache (much) more likely because dynamically allocated block is contiguous.

What About an Array of Size Zero?

You can't. Defining an array of constant size zero is illegal.

C++
struct Foo
{
  // ..
  size_t size;
  // Error: cannot allocate an array of constant size zero
  char data[0];
};

What About an int?

Yes, you can. But then you have to write more complex expression to access array elements. p->data[i] is more convenient and readable than (&p->data)[i], isn’t it?

C++
struct Foo
{
  size_t size;
  int data;
};

// 1. operator-> has a higher precedence than operator&
// 2. it's illegal to say "p->data[i]" because array subscript
// operator[] can only be used with a pointer among all other
// build-in types. User-defined type can overload this operator
// though.
for (int i = 0; i < p->size; ++i) ((&p->data)[i]) = i;

Flexible Array Member

C99 has a new language feature called “flexible array member”. It’s quite similar to the struct hack except an empty bracket [].

C++
struct Foo
{
  size_t size;
  int data[]; // FLA
};

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead National Instruments
China China
Senior software engineer at National Instruments, to implement various Ethernet-based industrial protocols, e.g., EtherCAT. Favorite languages are C/C++ and Python. For fun, I like watching films (sci-fi, motion), walking, and various reading.

Comments and Discussions

 
QuestionAnyone using such hacks deserves what they get! Pin
Daniel Pfeffer6-Oct-16 0:12
professionalDaniel Pfeffer6-Oct-16 0:12 
AnswerRe: Anyone using such hacks deserves what they get! Pin
Eric Z (Jing)6-Oct-16 3:06
Eric Z (Jing)6-Oct-16 3:06 
QuestionMuch cleaner way to do this without the danger of byte alignment Pin
leon de boer1-Oct-16 2:20
leon de boer1-Oct-16 2:20 
What you are doing in dangerous with byte alignment on processors and highly not recommend .. NOT EVER.

I will show you the trick we use commercially that allows you to have data above and below your interface you just need a single self ptr into the struct.

So your .H file defines the public aspects of the struct and we refer to it as the STRUCT INTERFACE you dont need it's size but I will leave it
typedef struct _Foo {
	void* self;     // untyped pointer to the full data block 
					//..
	size_t size;
} FOO;				// Type define the struct

However in the .C you define and extended version of the struct which itself declares the C header.
This trick means any change in the .H file automatically appears in the .c file private type.
You will note you can have data above and below the interface struct
typedef struct _PrivFoo {
	int Data1;               // You can also define private data above the interface
	
	FOO PublicInterface;	// You now have everything the public interface (.h file) defines

	int Data2;               // Here is your private data below the interface
} PRIVFOO;                  // Type define the private struct

Foo never really exists as such it's just an interface and you provide the function to create it which looks like this
FOO* CreateFoo (void) {
	PRIVFOO* p = (PRIVFOO*) malloc(sizeof(struct _PrivFoo)); // Allocate bigger data record memory
	if (p) {									// Check allocate worked
		FOO* FooInt = &p->PublicInterface;		// Make pointer to reduce typing ... I am lazy
		memset(p, 0, sizeof(struct _PrivFoo));	// Zero memory allocated
		FooInt->self = p;		   			    // Set the self pointer in the interface
		return FooInt;		                   // return the interface pointer
	}
	return 0;
}


To prove everything is ok .. try creating one it looks normal
FOO* myFoo = CreateFoo();  // Create a foo


A dig around with a debugger will quickly show you that what you think of as FOO is part of a much bigger memory block including the private data.

Now to access the hidden data you provide interface function that take FOO but internally convert then to PRIVFOO
but that all usually only exists inside the .C file. You never expose PRIVFOO to the outside world.

void SetData1(FOO myFoo, int a) {
	PRIVFOO* p = myFoo.self;					// Get the self pointer
	p->Data1 = a;								// Set private data1
}

int GetData1(FOO myFoo) {
	PRIVFOO* p = myFoo.self;					// Get the self pointer
	return(p->Data1);							// Get private data1
}


If you want to free the memory of a FOO remember its free(FOO->self) not free(FOO).
In vino veritas

AnswerRe: Much cleaner way to do this without the danger of byte alignment Pin
Eric Z (Jing)1-Oct-16 5:38
Eric Z (Jing)1-Oct-16 5:38 
GeneralRe: Much cleaner way to do this without the danger of byte alignment Pin
leon de boer2-Oct-16 16:42
leon de boer2-Oct-16 16:42 
GeneralRe: Much cleaner way to do this without the danger of byte alignment Pin
Eric Z (Jing)2-Oct-16 19:31
Eric Z (Jing)2-Oct-16 19:31 
GeneralRe: Much cleaner way to do this without the danger of byte alignment Pin
leon de boer5-Oct-16 1:01
leon de boer5-Oct-16 1:01 
GeneralRe: Much cleaner way to do this without the danger of byte alignment Pin
Eric Z (Jing)5-Oct-16 4:05
Eric Z (Jing)5-Oct-16 4:05 
GeneralRe: Much cleaner way to do this without the danger of byte alignment Pin
Rick York6-Oct-16 4:46
mveRick York6-Oct-16 4:46 
GeneralRe: Much cleaner way to do this without the danger of byte alignment Pin
Eric Z (Jing)6-Oct-16 4:51
Eric Z (Jing)6-Oct-16 4:51 
QuestionI miss the need for such a hack Pin
Ivor O'Connor26-Dec-13 8:17
Ivor O'Connor26-Dec-13 8:17 
AnswerRe: I miss the need for such a hack Pin
Eric Z (Jing)26-Dec-13 15:03
Eric Z (Jing)26-Dec-13 15:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.