Reputation: 51
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
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
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.
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