malloc 的这种使用会导致 "optional" 结构字段吗?

Could this use of malloc lead to an "optional" struct field?

我正在实现一个结构,我需要(在运行时)有一个可选字段。
所以我想到了这个:

//...
#include <stdlib.h>

struct test {
  int x;  // Must be
  int y;  // Optional (Must be the last field..(?))
};

int main(int argc, char **argv) {
    // With the optional field
    struct test *a = malloc(sizeof(*a));
    a->x = 11;
    a->y = 55;
    
    // Without the optional field
    struct test *b = malloc(sizeof(*b) - sizeof(int));
    b->x = 22;
    
    // ...
    
    free(a);
    free(b);
    return 0;
}

这段代码可以满足我的要求吗?
可能会添加一个位域来检查是否有可选字段。

此外,如果提议的解决方案有效,如果这是针对多个项目(> 100000)的列表实施的,这样做是否会更好以节省内存?

Could this code do what I ask?

嗯,它可以,但你不能依赖它。不要这样做;这不是编写正确程序的方法。

当您编写 b->x = 22; 时,编译器有权表现得好像在 b 处有一个完整的 struct test。您可能会想,“我只是将 22 放入成员 x 的字节中”,但编译器可能会使用“存储八个字节”指令:

  • 考虑一些内存被组织成八字节组的架构。总线只能读取和写入整个八字节块。
  • 由于无法在硬件中写入四个字节,将四个字节写入内存需要读取八个字节,在处理器寄存器中操作它们以在其中四个字节中插入所需的值,然后将八个字节写回内存.
  • 编译器要优化b->x = 22;,它知道y还没有设置,所以允许有任何值。因此,编译器不会使用低效的四字节写入序列,而是生成一个八字节存储,将 22 放入 b->x 并将 0 放入 b->y.

然后这会失败,因为编译器刚刚将 0 写入可能正在用于其他用途的内存,因为它不是您为 b.[=20= 分配的 space 的一部分]

“如果你对编译器撒谎,它会报复的。” — 亨利·斯宾塞

我认为您提出的解决方案很危险。使用两种不同的结构:

struct test_x {
   int x;
};

struct test_xy {
  int x;
  int y;
};

要么有两个数组,要么将 void * 与鉴别器(例如标记指针)一起存储到其中一个。另一种选择是为可选元素使用指针,但 sizeof(int *) 至少在我的盒子上与 sizeof(int) 相同,所以只会让事情变得更大。

如果所有 y 成员都是可选的,请考虑列布局,或者您可以对数据进行排序,以便所有 xy 元素排在第一位:

struct test_column {
  int *x;
  int *y;
};

struct test_column t = {
  .x = malloc(100000 * sizeof(int)),
  .y = 0

万一它对你没有帮助,但联合是两个结构共享内存的标准方式,所以每个元素的大小是 max(sizeof(test_xy), sizeof(test_x)) 而不是 sizeof(test_xy) + sizeof(test_x).

最后,考虑压缩,特别是如果您使用 test_column 格式。

您正在尝试的内容不符合 C 标准,因为您正在尝试使用没有为其分配足够内存的类型 struct test 的对象,即使您是只访问为其分配内存的字段。它 可能 有效,但你不能依赖它。

可以做的是利用灵活数组成员:

struct test {
  int x;
  int y[];
};

在这样的结构中,sizeof(struct test) 不包括最后一个成员。您可以通过为结构分配 space 以及您想要的最后一个成员的尽可能多的数组元素来使用这样的结构。例如:

struct test *b = malloc(sizeof(*b) + sizeof(int));
b->x = 1;
b->y[0] = 2;

您需要使用数组索引来访问最后一个成员,但这是一种以符合标准的方式执行您想要的操作的方法。

如果你不想要最后一个成员,你可以这样做:

struct test *b = malloc(sizeof(*b));
b->x = 1;