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

3
在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_storage和my_context.buffer.storage的指针相同,在Linux ... 3.2.0-4-amd64 #1 SMP Debian 3.2.65-1+deb7u2 x86_64 GNU/Linux上使用gcc(Debian 4.7.2-5)4.7.2。
[编辑2] 在一个后来被删除的答案中,我被引用了C99标准第6.7.8-19节:“初始化应按初始化程序列表顺序进行...”这是否意味着?
struct Context my_context = {
  .my_storage = {0},
  .buffer = {
    .size = 0,
    .storage = my_context.my_storage,
  },
};

这是绝对安全的吗?我是这样理解的。

[编辑3] 下面是一个完整的工作示例。

#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

1
为什么你需要存储指针?似乎有些多余。为什么不直接使用 struct Context { size_t size; int8_t my_storage[10];}; - Lundin
1
@Lundin 在我的实际代码中,有一个环形缓冲区{.head=0, .tail=0, .size=0, .storage=...,并且该环形缓冲区通过其自己的一组已提供的函数进行操作。 - evading
关于 Edit2,我认为你需要在结构体的顶部存储缓冲区,然后你的修改是正确的。这样可以确保在将其分配给指针之前,缓冲区具有已分配的 RAM 地址。 - LPs
2个回答

2

是的,这是可以的。假设my_context具有自动存储期,它的生命周期始于进入关联块时,并且在其生命周期内它具有一个常量地址(6.2.4对象的存储期/2)。(如果它具有静态或线程存储期,则其生命周期分别为整个程序或线程的持续时间)。

因此,my_context.my_storagemy_context的生命周期内也具有一个恒定的地址,因此通过数组到指针衰减方式获取其地址,用于初始化my_context.buffer.storage将会与在完成my_context的初始化后所得到的值相同。

还要注意,my_context作用域始于其声明完成的点,即初始化器的=之前,因此在其初始化器中引用它也是可以的。


我应该说明一下,但是正如[edit3]所显示的那样,my_context是全局的。因此没有自动存储期限,对吧? - evading
@逃避 也可以;那只是延长了生命周期。 - ecatmur

1
这实际上与指定初始化程序和初始化顺序无关。你实际上问的是这样做是否明确定义:
typedef struct
{
  int* ptr;
  int  val; 
} struct_t;

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

是的,我认为这没有问题。编译器在尝试初始化变量s之前必须在内存中分配地址。结构成员分配或初始化的顺序不应该影响结果。


然而,编写初始化列表时,其中一个结构体的值依赖于同一结构体的另一个值是不安全的!C11 6.7.9/23说:
“初始化列表表达式的评估相对于彼此具有不确定的顺序,因此任何副作用发生的顺序是未指定的。”
将值分配给变量是一种“副作用”。因此,像这样的代码是不安全的:
typedef struct
{
  int  val1; 
  int  val2;
} struct_t;

struct_t s = {0, s.val1};

由于编译器可能会像这样“评估”初始化程序列表的两个表达式:
  • s.val1 -> 评估为val1的内容(垃圾值,尚未初始化)
  • 0 -> 评估为0
  • 将评估的值(0)写入val1,在执行以下操作之前保证发生:
  • 将评估的值(垃圾值)写入val2
因此,尽管初始化顺序是有保证的,但初始化列表的评估顺序却不是。当然,编译器可能已经决定先评估0表达式,然后一切都可以正常工作。
最重要的是,不要编写晦涩难懂的初始化列表。

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