1

I see this in code sometimes:

struct S
{
    int count; // length of array in data
    int data[1];
};

Where the storage for S is allocated bigger than sizeof(S) so that data can have more space for its array. It is then used like:

S *s;

// allocation

s->data[3] = 1337;

My question is, why is data not a pointer? Why the length-1 array?

5
  • Doesn't int data[1] represent a int *data? Commented Sep 30, 2010 at 17:14
  • Are you saying the allocation is something like s=malloc(sizeof(S)+sizeof(int)*4) to create an array of size 5? Commented Sep 30, 2010 at 17:16
  • 2
    @phimuemue: No, it doesn't. Array is not a pointer. Commented Sep 30, 2010 at 17:25
  • 1
    Related question: Is the "struct hack" technically undefined behavior? Commented Sep 30, 2010 at 18:19
  • 1
    A variable length array (VLA) is something completely different: An array whose size is not an constant integer expression. You probably mean flexible array member, the legal successor to the dubious struct hack. Commented Sep 30, 2010 at 20:21

6 Answers 6

9

If you declare data as a pointer, you'll have to allocate a separate memory block for the data array, i.e. you'll have to make two allocations instead of one. While there won't be much difference in the actual functionality, it still might have some negative performance impact. It might increase memory fragmentation. It might result in struct memory being allocated "far away" from the data array memory, resulting in the poor cache behavior of the data structure. If you use your own memory management routines, like pooled allocators, you'll have to set up two allocators: one for the struct and one for the array.

By using the above technique (known as "struct hack") you allocate memory for the entire struct (including data array) in one block, with one call to malloc (or to your own allocator). This is what it is used for. Among other things it ensures that struct memory is located as close to the array memory as possible (i.e. it is just one continuous block), so the cache behavior of the data structure is optimal.

Sign up to request clarification or add additional context in comments.

10 Comments

It's not necessary to make two allocations, if data is a pointer. One can allocate memory like this: malloc(sizeof(S) + N*sizeof(int)) and set the pointer to: s.data = (int*)(&s+1). The problem is that the pointer has to be set and accessing data requires one more level of indirection.
@Maciej Hehl: Yes, it is possible to do it that way as well. It is often done that way when you have multiple variable length arrays in the struct. However, it is not as easy as you suggest. In general you have to make sure that the array memory is properly aligned. Your (int *) (ptr_to_s + 1) does not ensure that. Two allocations would solve that problem automatically. "Struct hack" does as well. But with pointer and one allocation it is your responsibility to take care of alignment.
You must be thinking about the "braindead" version of struct hack where 0-sized array is used instead of 1-sized array - that would indeed require a compiler extension. But nobody forces you to use that strange 0-sized version. Just do it properly and use 1-sized array in C89/90 and size-less array declaration in C99.
@Maciej Hehl: So what? What is the importance of that example? Where do you see the problem?
@Maciej also if you do that pointer version, simply memcpy the struct to another allocated block won't do it. You will have to adjust the pointer manually for each copy you do, and writing out the memory block to the network/a file will include writing out the bytes of the pointer, which is usually not desired.
|
6

Raymond Chen wrote an excellent article on precisely why variable length structures chose this pattern over many others (including pointers).

He doesn't directly comment on why a pointer was chosen over an array but Steve Dispensa provides some insight in the comments section.

From Steve

typedef struct _TOKEN_GROUPS { 
DWORD GroupCount; 
SID_AND_ATTRIBUTES *Groups; 
} TOKEN_GROUPS, *PTOKEN_GROUPS; 

This would still force Groups to be pointer-aligned, but it's much less convenient when you think of argument marshalling.

In driver development, developers are sometimes faced with sending arguments from user-mode to kernel-mode via a METHOD_BUFFERED IOCTL. Structures with embedded pointers like this one represent anything from a security flaw waiting to happen to simply a PITA.

Comments

1

It's done to make it easier to manage the fact that the array is sequential in memory (within the struct). Otherwise, after the memalloc that is greater than sizeof(S), you would have to point 'data' at the next memory address.

Comments

1

Because it lets you have code do this:

struct S
{
    int count; // length of array in data
    int data[1];
};

    struct S * foo;

    foo = malloc(sizeof(struct S) + ((len - 1)*sizeof(int)) );
    strcpy(foo->data, buf);

Which only requires one call to malloc and one call to free.

This is common enough that the C99 standard allows you do not even specify a length of the array. It's called a flexible array member.

From ISO/IEC 9899:1999, Section 6.7.2.1, paragraph 16: "As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member." called a flexible array member."

struct S
{
    int count; // length of array in data
    int data[];
};

And gcc has allowed 0 length array members as the last members of structs as an extension for a while.

1 Comment

isn't there a 1 too much in your second code example? that member has complete array type...
0

Because of different copy semantics. If it is a pointer inside, then the contents have to explicitly copied. If it is a C-style array inside, then the copy is automatic.

1 Comment

The OP is obviously describing a "struct hack" ("...where the storage for S is allocated bigger than sizeof(S)..."). You can't use built-in copying (i.e. assignment) to copy "struct hack"-ed structs. You have to copy them explicitly (with memcpy or something like that).
0

Incidentally, I don't think there's any guarantee that using a length-one array as something longer is going to work. A compiler would be free to generate effective-address code that relies upon the subscript being no larger than the specified bound (e.g. if an array bound is specified as one, a compiler could generate code that always accesses the first element, and if it's two, on some platforms, an optimizing compiler might turn a[i] into ((i & 1) ? a[1] : a[0]). Note that while I'm unaware of any compilers that actually do that transform, I am aware of platforms where it would be more efficient than computing an array subscript.

I think a standards-compliant approach would be to declare the array as [MAX_SIZE] and allocate sizeof(struct S)-(MAX_SIZE-len)*sizeof(int) bytes.

2 Comments

@Kristopher Johnson: I wrote the last answer there, but it got no comments. None of the other comments seemed to mention why a compiler might have trouble with the struct hack, but it would seem entirely reasonable for a compiler to generate code which assumes an array will only be indexed within the specified bounds. I wish the authors of the standard had allowed zero-length arrays, since they could be interpreted as the one situation where the struct hack was permissible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.