Author Topic: Weird ideas #2: variable sized structures  (Read 3062 times)


  • Newbie
  • *
  • Posts: 48
    • View Profile
Weird ideas #2: variable sized structures
« on: June 19, 2014, 02:50:04 AM »
A common idiom in C is something like this:

typedef struct {
   int foo_count;
   struct foo[0];
} foo_list;

That foo[0] part (GCC syntax, MSVC is foo[]) means "don't allocate any space for this, but let me access the array of elements later".  It is heavily used for binary protocols and things where you want array access semantics to the foo elements, but you do not know in advance how many there might be and you are passed a blob of data.

I find the 0 or empty brackets ([]) to be something of a hack.  I would prefer a small syntactic change to use foo[...] or something like that.    At least to my eye, this means more clearly "we do not know how many there will be at this time".

Note that this is different from a dynamically sized array.  For that, you need a hidden count of the actual array size.  In fact, the implementation of a dynamic array might very well look like the example above.  I am not sure if using the same syntax for both would be a good idea.



  • Jr. Member
  • **
  • Posts: 87
    • View Profile
Re: Weird ideas #2: variable sized structures
« Reply #1 on: June 19, 2014, 08:11:40 AM »
Hmm interesting.

C2 currently has something called 'incremental arrays'. This means you can do:

  • array;

array += { 1, 2 }
...(other code)..
array += { 4 }

elemsof(array) would return 3 here. Maybe we can re-use the same '+' symbol?


  • Newbie
  • *
  • Posts: 48
    • View Profile
Re: Weird ideas #2: variable sized structures
« Reply #2 on: June 20, 2014, 11:05:19 PM »
I  talked about two things in my original post.

The first is using the last element in a struct as an zero-element array.  In C this idiom is used to handle variable length arrays of data in binary data:

Code: [Select]
uint8_t buf[4096];

/* read data into buf */

typedef struct {
    int element_count;
    struct foobar[0];
} *foobars;


foobars *foos = (foobars)buf;

for(int i=0; i < foos->element_count; i++) {
    .... do something with foos->foobar[i] ...

This idiom is very common in protocols.  Sometimes there is a lot more than just a length count at the beginning of the struct.  Here's a really long example from one of my projects:

Code: [Select]
typedef struct {
    /* encap header */
    uint16_t encap_command;    /* ALWAYS 0x006f Unconnected Send*/
    uint16_t encap_length;   /* packet size in bytes - 24 */
    uint32_t encap_session_handle;  /* from session set up */
    uint32_t encap_status;          /* always _sent_ as 0 */
    uint64_t encap_sender_context;  /* whatever we want to set this to, used for
                                     * identifying responses when more than one
                                     * are in flight at once.
    uint32_t encap_options;         /* 0, reserved for future use */

    /* Interface Handle etc. */
    uint32_t interface_handle;      /* ALWAYS 0 */
    uint16_t router_timeout;        /* in seconds */

    /* Common Packet Format - CPF Unconnected */
    uint16_t cpf_item_count;        /* ALWAYS 2 */
    uint16_t cpf_nai_item_type;     /* ALWAYS 0 */
    uint16_t cpf_nai_item_length;   /* ALWAYS 0 */
    uint16_t cpf_udi_item_type;     /* ALWAYS 0x00B2 - Unconnected Data Item */
    uint16_t cpf_udi_item_length;   /* REQ: fill in with length of remaining data. */

    /* CM Service Request - Connection Manager */
    uint8_t cm_service_code;        /* ALWAYS 0x54 Forward Open Request */
    uint8_t cm_req_path_size;       /* ALWAYS 2, size in words of path, next field */
    uint8_t cm_req_path[4];         /* ALWAYS 0x20,0x06,0x24,0x01 for CM, instance 1*/

    /* Forward Open Params */
    uint8_t secs_per_tick;        /* seconds per tick */
    uint8_t timeout_ticks;        /* timeout = srd_secs_per_tick * src_timeout_ticks */
    uint32_t orig_to_targ_conn_id;  /* 0, returned by target in reply. */
    uint32_t targ_to_orig_conn_id;  /* what is _our_ ID for this connection, use ab_connection ptr as id ? */
    uint16_t conn_serial_number;    /* our connection serial number ?? */
    uint16_t orig_vendor_id;        /* our unique vendor ID */
    uint32_t orig_serial_number;    /* our unique serial number */
    uint8_t conn_timeout_multiplier;/* timeout = mult * RPI */
    uint8_t reserved[3];            /* reserved, set to 0 */
    uint32_t orig_to_targ_rpi;      /* us to target RPI - Request Packet Interval in microseconds */
    uint16_t orig_to_targ_conn_params; /* some sort of identifier of what kind of PLC we are??? */
    uint32_t targ_to_orig_rpi;      /* target to us RPI, in microseconds */
    uint16_t targ_to_orig_conn_params; /* some sort of identifier of what kind of PLC the target is ??? */
    uint8_t transport_class;        /* ALWAYS 0xA3, server transport, class 3, application trigger */
    uint8_t path_size;              /* size of connection path in 16-bit words
                                     * connection path from MSG instruction.
                                     * EG LGX with 1756-ENBT and CPU in slot 0 would be:
                                     * 0x01 - backplane port of 1756-ENBT
                                     * 0x00 - slot 0 for CPU
                                     * 0x20 - class
                                     * 0x02 - MR Message Router
                                     * 0x24 - instance
                                     * 0x01 - instance #1.

    uint8_t conn_path[ZLA_SIZE];    /* connection path as above */
} eip_forward_open_request;

The last field, conn_path, uses a macro ZLA_SIZE that is either blank (MSVC) or 0 (GCC).  The second to last field actually contains the length of the connection path.  After I read in a packet, I cast to a pointer to this struct and then I can access all the fields, including the connection path, as if the struct was variable size.

The command I had about variable length arrays was not really related to this.  Sorry for the confusion.