3

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.

4
  • Not for data, unless it has a hard-coded length. Commented Nov 29, 2020 at 1:18
  • 1
    Although you could if data was last Commented Nov 29, 2020 at 1:20
  • I believe this would work if data was at the end of the structure. Commented Nov 29, 2020 at 1:20
  • 2
    Move data[] to the end and lookup flexible arrays. Commented Nov 29, 2020 at 1:21

2 Answers 2

2

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.

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

2 Comments

Yep! That is pretty much what I was hoping to do, except Struct *s = (Struct*)buf. It's too bad that it isn't possible, it isn't a big deal but I was curious
Well, it is, with the caveats I mentioned. gcc on an x86 or x64 could do it.
1

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.

Comments

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.