zachm0
zachm0

Reputation: 51

Casting a struct onto variable length buffer

Is it possible to create struct that I can cast to a buffer of variable size? For example if I have some data that looks something like this:

0  1  2  3  4  5  6  7  8
+--+--+--+--+--+--+--+--+
|                       |
/                       /
/          DATA         /
|                       |
+--+--+--+--+--+--+--+--+
|          NAME         |
+--+--+--+--+--+--+--+--+
|          TIME         |
+--+--+--+--+--+--+--+--+
|          ETC          |
+--+--+--+--+--+--+--+--+

Would it be possible to have a struct similar to this

typedef struct{
    uint8_t data[];
    uint8_t name;
    uint8_t time;
    uint8_t etc;
}Struct;

and then resize Struct.data at runtime to I can cast the structure directly onto the buffer?

I know I could just make a struct that uses pointers and have them point to parts of the buffer, but I was curious if this is possible because it would simplify my code and make it easier to read/use.

Upvotes: 3

Views: 211

Answers (2)

user3657941
user3657941

Reputation:

If you can move the variable length buffer to the end of the structure, and you don't care about portability, you can do this. This example relies on #pragma pack to align fields on 8 byte boundaries.

variable_length.h

#ifndef VARIABLE_LENGTH_H
#define VARIABLE_LENGTH_H

#include <stdint.h>

#pragma pack(8)

typedef struct {
    uint8_t name;
    uint8_t time;
    uint8_t etc;
    size_t size;
} header_t;

typedef struct {
    uint8_t name;
    uint8_t time;
    uint8_t etc;
    size_t size;
    uint8_t data[];
} data_t;

#pragma pack()

#endif

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "variable_length.h"

header_t *create_buffer(char *buffer_name) {
    size_t buffer_size = strlen(buffer_name) + 1;
    size_t size = sizeof(header_t) + buffer_size;
    data_t *result = calloc(1, size);
    if (NULL == result) {
        perror("Could not allocate memory for buffer");
        exit(1);
    }
    result->name = 'A';
    result->time = 1;
    result->etc = 'x';
    result->size = buffer_size;
    strncpy((char *) result->data, buffer_name, buffer_size);
    return (header_t *) result;
}

int main(void) {
    header_t *result = create_buffer("example buffer");
    data_t *example = (data_t *) result;
    printf("name = %c\n", example->name);
    printf("time = %d\n", example->time);
    printf("etc  = %c\n", example->etc);
    printf("size = %lu\n", example->size);
    for (int index = 0; index < example->size; index++) {
        printf("%c", example->data[index]);
    }
    printf("\n\n");
    printf("address of name = %p\n", &example->name);
    printf("address of time = %p\n", &example->time);
    printf("address of etc  = %p\n", &example->etc);
    printf("address of size = %p\n", &example->size);
    printf("address of data = %p\n", example->data);
    return 0;
}

Output

name = A
time = 1
etc  = x
size = 15
example buffer

address of name = 0xf20260
address of time = 0xf20261
address of etc  = 0xf20262
address of size = 0xf20268
address of data = 0xf20270

Notes

As you can see, the address of size is 8 more than the address of name, and the address of data is 8 more than the address of size. If your target is a 32-bit architecture, you could use #pragma pack(4).

Warning

If you are trying to access addresses that are not on machine word boundaries, this might generate very slow code on x86 and x64 architectures, and code that crashes with SIGBUS on RISC architectures.

Trivia

I first saw structure definitions like this in Microsoft driver code.

Upvotes: 1

ikegami
ikegami

Reputation: 385819

It sounds like you're trying to do the following:

typedef struct {
   uint8_t name;
   uint8_t time;
   uint8_t etc;
} Struct;

char *buf = ...;

Struct *s = (Struct*)(buf + data_len);

... s->name ...

There are two problems with this.

  1. Alignment. You can't just place a structure at any address you want on every machine, so the concept is fundamentally flawed unless you're targeting a specific machine as well.
  2. Padding. The compiler is free to place padding between the fields of the structure, so the concept is fundamentally flawed unless you're targeting a specific compiler and tell that compiler to not use any padding.

In short, take a different approach.

typedef struct {
   uint8_t name;
   uint8_t time;
   uint8_t etc;
} Struct;

char *buf = ...;
buf += data_len;

Struct s;
memcpy(&s.name, buf, sizeof(s.name));  buf += sizeof(s.name);
memcpy(&s.time, buf, sizeof(s.time));  buf += sizeof(s.time);
memcpy(&s.etc,  buf, sizeof(s.etc));   buf += sizeof(s.etc);

... s.name ...

Checking to make sure you don't read beyond the end of buf is left to you.

Upvotes: 2

Related Questions