将成员地址分配给结构中的其他成员

Assign member address to other member in struct

下面的 C 语言安全吗?

struct Buffer {
  size_t size;
  int8_t *storage;
};

struct Context {
  struct Buffer buffer;
  int8_t my_storage[10];
};

struct Context my_context = {
  .buffer = {
    .size = 0,
    .storage = my_context.my_storage,
  },
  .my_storage = {0},
};

我正在使用微控制器,我不想必须使用 malloc。另外,对我来说,收集结构中的所有内容比将存储作为上下文之外的单独变量更好。

[编辑1] 我已经测试过它并且它可以编译和工作,因为指向 my_context.my_storagemy_context.buffer.storage 的指针是相同的,gcc (Debian 4.7.2-5) 4.7.2Linux ... 3.2.0-4-amd64 #1 SMP Debian 3.2.65-1+deb7u2 x86_64 GNU/Linux

[编辑2] 在后来被删除的答案中,我提到了 C99 标准第 6.7.8-19 节 "The initialization shall occur in initializer list order..." 这是否意味着

struct Context my_context = {
  .my_storage = {0},
  .buffer = {
    .size = 0,
    .storage = my_context.my_storage,
  },
};

保证安全吗?我是这么理解的。

[edit3] 下面是一个完整的工作示例。

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

struct Buffer {
  size_t size;
  int8_t *storage;
};

struct Context {
  struct Buffer buffer;
  int8_t my_storage[10];
};

struct Context my_context = {
  .buffer = {
    .size = 0,
    .storage = my_context.my_storage,
  },
  .my_storage = {0},
};

int
main(void)
{
  printf ("ptr points to: %" PRIXPTR "\n", (uintptr_t)my_context.buffer.storage);
  printf ("storage is at: %" PRIXPTR "\n", (uintptr_t)my_context.my_storage);
}

>> ./test
ptr points to: 600950
storage is at: 600950

是的,这很好。假设 my_context 具有自动存储持续时间,其生命周期从进入关联块时开始,并且在其生命周期内具有恒定地址(6.2.4 对象的存储持续时间/2 ). (如果它具有静态或线程存储持续时间,则其生命周期分别延长整个程序或线程的持续时间)。

由此可见 my_context.my_storagemy_context 的生命周期内也有一个常量地址,因此将其地址(通过数组到指针衰减)用于 my_context.buffer.storage 的初始化将给出与 my_context 初始化完成后相同的值。

另请注意,my_contextscope 开始于其声明完成的点,即在初始化程序的 = 之前,所以在它的初始化器中引用它也是可以的。

这与指定初始化器和初始化顺序没有任何关系。您实际上要问的是这样的事情是否定义明确:

typedef struct
{
  int* ptr;
  int  val; 
} struct_t;

struct_t s = {&s.val, 0};

是的,我不明白为什么不应该这样。在尝试初始化之前,编译器必须在内存中的一个地址分配 s。分配或初始化结构成员的顺序无关紧要。


However,编写初始化列表,其中结构的一个 value 依赖于另一个 value相同结构的是不安全的! C11 6.7.9/23 说:

The evaluations of the initialization list expressions are indeterminately sequenced with respect to one another and thus the order in which any side effects occur is unspecified.

给变量赋值是"side effect"。所以像这样的代码是不安全的:

typedef struct
{
  int  val1; 
  int  val2;
} struct_t;

struct_t s = {0, s.val1};

因为编译器可能会像这样计算初始化列表的两个表达式:

  • s.val1 -> 计算出val1的内容(垃圾,还没有初始化)
  • 0 -> 评估为 0
  • 将评估值 (0) 写入 val1,保证发生在:
  • 将评估值(垃圾)写入val2

所以即使初始化顺序是有保证的,初始化列表的求值顺序也不是。当然,编译器可能已经决定首先评估 0 表达式,然后一切都会正常工作。

最重要的是,不要编写晦涩难懂的初始化列表。