将结构体强制转换为可变长度缓冲区

3

是否可以创建结构体,使其可以转换为可变大小的缓冲区?举个例子:如果我有一些数据长这样:

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

是否可以创建一个类似于以下结构的结构体:

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

那么我能在运行时调整 Struct.data 的大小,以便我可以直接将结构体转换到缓冲区吗?

我知道我可以创建一个使用指针的结构体,让它们指向缓冲区的不同部分,但我很好奇是否有可能实现这一点,因为它会简化我的代码并使其更易于阅读/使用。


除非数据具有硬编码长度,否则不适用于“数据”。 - Fiddling Bits
1
尽管如果data是最后一个,你可以这样做。 - ikegami
如果“data”位于结构的末尾,我相信这将起作用。 - Daniel Walker
2
data[] 移动到末尾并查找灵活数组 - dxiv
2个回答

2

看起来您正在尝试做以下事情:

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

char *buf = ...;

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

... s->name ...

这个方法有两个问题。

  1. 对齐。你不能在任何机器上随意放置结构体,因此该概念基本上是有缺陷的,除非你也针对特定的机器。
  2. 填充。编译器可以在结构体字段之间放置填充,因此该概念基本上是有缺陷的,除非你针对特定的编译器并告诉该编译器不使用任何填充。

简而言之,请采用不同的方法。

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 ...

需要确保您不会读取超出buf结尾的内容,这需要由您自行检查。


是的!这基本上就是我希望做的,除了Struct *s = (Struct*)buf。很遗憾它不可能,这不是什么大问题,但我很好奇。 - zachm0
好的,就像我提到的那样,它是可以做到的。在x86或x64上使用gcc可以实现。 - ikegami

1
如果您可以将可变长度缓冲区移动到结构的末尾,并且不关心可移植性,则可以这样做。此示例依赖于#pragma pack将字段对齐到8字节边界。

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;
}

输出

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

注释

如您所见,size的地址比name的地址多8个字节,data的地址比size的地址多8个字节。如果您的目标是32位架构,则可以使用#pragma pack(4)

警告

如果您试图访问不在机器字边界上的地址,则这可能会在x86和x64架构上生成非常缓慢的代码,并在RISC架构上崩溃并出现SIGBUS

趣闻

我第一次看到这样的结构定义是在Microsoft驱动程序代码中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接