Flexible Array Members (FAM)

Motivation

While working on a SIP parser, I encountered a performance issue: allocating memory for each string value in a SIP message was proving costly. To optimize, I aimed to use a single malloc call to allocate memory for the entire SIP message, including all associated strings.

Here's a simplified version of the struct I was using:

struct cmsc_SipMessage {
  uint32_t present_mask;  // Indicates which fields are present
  bool is_request;
  uint32_t sip_status_code; 
  enum cmsc_SipMethod sip_method;
  enum cmsc_SipMsgType sip_msg_type;
  struct cmsc_SipProtoVer sip_proto_ver;
  struct cmsc_SipVia via_l;
  // More fields...
};

The goal was to embed a buffer within this structure, avoiding multiple heap allocations for strings. However, I also wanted to avoid wasting memory by allocating a large fixed buffer for messages that might be small (e.g., \~500 characters).

The Problem with Fixed Buffers

Allocating a large buffer (e.g., 2–3 KB) for every message would be wasteful, especially for smaller SIP messages. But shrinking the buffer introduces the risk of overflow. So how do we keep the flexibility and the one-malloc approach?

Enter Flexible Array Members

C99 introduced Flexible Array Members—a powerful feature that allows you to define a variable-sized array as the last member of a struct.

Here’s how to modify the struct:

struct cmsc_SipMessage {
  // Previous fields...
  struct cmsc_SipVia via_l;
  // More fields...
  char buffer[];  // Flexible array member
};

The key point: the buffer[] does not contribute to sizeof(struct cmsc_SipMessage). This allows you to dynamically allocate extra space:

malloc(sizeof(struct cmsc_SipMessage) + sizeof(char) * 500);

This allocation gives you space for the struct plus 500 bytes for the buffer.

Example Code

#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

struct cmsc_StringsBuffer {
  uint32_t buffer_size;
  uint32_t buffer_len;
  char buffer[];
};

int main(void) {
  struct cmsc_StringsBuffer *small_buffer =
      malloc(sizeof(struct cmsc_StringsBuffer) + (sizeof(char) * 255));
  if (!small_buffer) {
    puts("Cannot allocate memory for `small_buffer`");
    return ENOMEM;
  }
  small_buffer->buffer_size = 255;
  small_buffer->buffer_len = 0;

  for (int i = 0; i < small_buffer->buffer_size; i++) {
    small_buffer->buffer[i] = (i % 120) + 40;
  }

  printf("%s\n\n", small_buffer->buffer);
  free(small_buffer);

  struct cmsc_StringsBuffer *big_buffer =
      malloc(sizeof(struct cmsc_StringsBuffer) + (sizeof(char) * 1024));
  if (!big_buffer) {
    puts("Cannot allocate memory for `big_buffer`");
    return ENOMEM;
  }
  big_buffer->buffer_size = 1024;
  big_buffer->buffer_len = 0;

  for (int i = 0; i < big_buffer->buffer_size; i++) {
    big_buffer->buffer[i] = (i % 120) + 40;
  }

  printf("%s\n", big_buffer->buffer);
  free(big_buffer);

  return 0;
}

Limitations of Flexible Array Members

Using FAMs comes with important restrictions:

  • Must be the last member - No other field may follow the flexible array in the struct.
  • Only one per struct - A struct can have only one flexible array member.
  • Not counted in sizeof - The compiler treats the FAM as 0-length in size calculations. You must add the desired size manually in malloc.
  • No initializer support - You can’t initialize the array like { ..., {1, 2, 3} }.
  • Requires C99 or later - Not available in C89/C90. Some compilers support char buf[0] as a non-standard workaround.
  • Cannot be in unions - FAMs are not allowed inside union types.
  • Needs a size tracker - You'll need to manually store and track the array size, e.g., using buffer_size and buffer_len.

Conclusion

Flexible Array Members are a valuable tool when you need to manage variable-sized data efficiently, especially in performance-critical applications like SIP parsers. They reduce memory fragmentation and allocation overhead while maintaining clean and predictable memory usage.

With a clear understanding of their limitations and proper usage, FAMs can help you write both high-performance and maintainable C code.

Published:

Category: C

Comments

Leave a comment