使用 alignof 打包两个对象

Pack two objects using alignof

用第二个object的align得到最终的size来打包两个object是否符合标准?

我将这种方法用于 doubly linked list,但提取了相关部分:

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

struct node
{
    struct node *prev;
    struct node *next;
};

#define get_node(data, szof) ((struct node *)(((char *)data) + szof))

int main(void)
{
    size_t align = alignof(struct node);
    double *data;

    // Round size up to nearest multiple of alignof(struct node)
    size_t szof = (sizeof(*data) + (align - 1)) / align * align;
    // Pack `data` + `struct node`
    data = malloc(szof + sizeof(struct node));
    // Get node using a generic pointer to calculate the offset
    struct node *node = get_node(data, szof);

    *data = 3.14;
    node->prev = NULL;
    node->next = NULL;
    printf("%f\n", *data);
    free(data);
    return 0;
}

其中 data 可以是指向任何基本类型或复合类型的指针。

Is it conforming with the standard to pack two objects using the align of the second object to get the final size?

当然,提供的代码有效。

这里真的没有什么可写的,因为证明比反驳更难。指针值与引用类型正确对齐,没有未初始化的内存访问。如果你自己还记得对齐,那么你可以在不使用 struct.

的情况下编写整个程序

在实际代码中,我建议创建一个结构并让编译器自行计算[1]。我们有 offsetof.

struct double_and_node {
     double data;
     struct node node;
};

void *pnt = malloc(sizeof(double_and_node));
double *data = (struct node*)((char*)pnt + offsetof(struct double_and_node, data));
struct node *node = (struct node*)((char*)pnt + offsetof(struct double_and_node, data));

我猜你可以研究 container_of 并查看 C11 6.3.2.3p7

[1] 但实际上,如果是这样,无论如何都要使用该结构...:[=​​17=]

struct double_and_node *pnt = malloc(sizeof(double_and_node));
double *data = &pnt->data;
struct node *node = &pnt->node;

作为先前答案的扩展思路,可以使用 typeof()offsetof() defining/using 动态定义的结构,用于将数据类型与节点结构连接:

#include <stdio.h>
#include <stddef.h>

struct node
{
    struct node *prev;
    struct node *next;
};

#define LINKED_TYPE_SIZE(data) \
            sizeof(struct { typeof(data) f; struct node node; })

#define LINKED_TYPE_NODE(datap) \
  (struct node *)((char *)(datap) + offsetof(struct { typeof(*(datap)) f; struct node node; }, node))

int main(void)
{

  double v1;

  printf("size of linked double = %zu\n", LINKED_TYPE_SIZE(v1));
  printf("%p, %p\n", &v1, LINKED_TYPE_NODE(&v1));

  int v2;

  printf("size of linked int = %zu\n", LINKED_TYPE_SIZE(v2));
  printf("%p, %p\n", &v2, LINKED_TYPE_NODE(&v2));

  short int v3;

  printf("size of linked short int = %zu\n", LINKED_TYPE_SIZE(v3));
  printf("%p, %p\n", &v3, LINKED_TYPE_NODE(&v3));

  struct foo {
    int f1;
    char f2;
    int f3;
  } foo_struct;

  printf("size of linked foo = %zu\n", LINKED_TYPE_SIZE(foo_struct));
  printf("%p, %p\n", &foo_struct, LINKED_TYPE_NODE(&foo_struct));

  return 0;
}

前面的执行在 x86_64 Linux 桌面上给出以下结果:

$ gcc try.c -o try
$ ./try
size of linked double = 24
0x7ffdfbdf50f8, 0x7ffdfbdf5100
size of linked int = 24
0x7ffdfbdf50f4, 0x7ffdfbdf50fc
size of linked short int = 24
0x7ffdfbdf50f2, 0x7ffdfbdf50fa
size of linked foo = 32
0x7ffdfbdf5100, 0x7ffdfbdf5110

N.B.: 由于 typeof() 是一个非标准函数,也可以通过将数据类型作为参数显式传递给宏来摆脱它:

#define LINKED_TYPE_SIZE(type) \
            sizeof(struct { type f; struct node node; })

#define LINKED_TYPE_NODE(type, datap) \
  (struct node *)((char *)(datap) + offsetof(struct { type f; struct node node; }, node))

好吧,这很复杂。 ((char *)data) + szof 行可以说是根据 alignof(struct node)sizeof(double) 调用未定义的行为,但这不是很明显。

首先,让我们假设 double* data 实际上指向一个 double。然后我们将被允许通过字符类型指针检查这个对象,按照 6.3.2.3/7:

When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.

所以我们可以做 ((char *)data) + szof 而我们坚持实际 double。否则,如果我们超出 double 的范围,上面引用的特殊规则将不适用。

相反,我们应该遵守由加法运算符指定的指针算术规则。尽管这些规则希望您使用指向类型 double* 而不是 char*。这些规则并没有真正指定当您通过 char* 检查 double 并超出 sizeof(double) 字节时会发生什么。

所以 ((char *)data) + szof 超出 sizeof(double) 是有问题的 - 我认为无论你怎么说它都是未定义的行为。

那么这里还有另一个方面...如果 char 指针指向没有类型的东西怎么办? C 标准没有指定接下来会发生什么。这实际上就是代码正在做的事情。

因为碰巧,data = malloc(szof + sizeof(struct node)); 分配了一个没有声明或“有效类型”的原始段。 6.5 规则然后指出

If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value

并且在 *data = 3.14; 之前,您不会对实际内存进行左值访问,在这种情况下,内存将获得有效类型 double。这发生在指针算法之后。