为什么要在 c 中将指针转换为双指针?

Why to cast pointer to double pointer in c?

我正在阅读 C 中的 OOP,并且有这个 header:

#ifndef _NEW_H
#define _NEW_H

#include <stdarg.h>
#include <stddef.h>
#include <assert.h>

void *new (const void *type, ...);
void delete (void *item);

typedef struct
{
    size_t size;
    void *(*ctor)(void *self, va_list *app);
    void *(*dtor)(void *self);
    void *(*clone)(const void *self);
    int (*differ)(const void *self, const void *x);
} class_t;

inline void *new (const void *_class, ...)
{
    const class_t *class = _class;
    void *p = calloc(1, class->size);

    assert(p);
    *(const class_t **)p = class; //why would you cast to double pointer, when you immediately dereference it?

    if (class->ctor)
    {
        va_list args;
        va_start(args, _class);
        p = class->ctor(p, &args);
        va_end(args);
    }
    return p;
}

#endif //_NEW_H

现在我不明白这个表达:

*(const class_t **)p = class;

const class_t **类型是什么意思?它就像一个数组数组?但是如果我想要自定义 class(也就是说,不仅是指向结构 class_t 的指针,而是更多的“public”方法),整体 class 不是一个类型数组 class_t。那么,为什么我要将 void 指针转换为双指针并立即取消引用它呢?应该怎么理解?

书中关于那句话的内容: * (const struct Class **) p = class;

p points to the beginning of the new memory area for the object. We force a conversion of p which treats the beginning of the object as a pointer to a struct class_t and set the argument class as the value of this pointer.

calloc() 的调用用于分配未指定“class”类型的 1 个元素的数组,其中该类型的第一个成员应为 class_t* 指针(因此 class->size 必须至少为 sizeof(class_t*),但可以更高)。可能会使用 calloc() 而不是 malloc(),这样 class->size 表示的任何其他数据成员都将是 zero-initialized,否则将需要显式的 memset()

怪异的转换+取消引用只是为了让代码可以将输入 class 指针直接存储到该分配对象的第一个 class_t* 成员中。

可以使用 double-pointer 访问数组。取消引用此类指针可为您提供数组中第一个元素的地址。在这种情况下,它恰好与 class_t* 成员的地址相同。

在 OOP 术语中,对象在内存中的布局通常以指向对象 class 的 vtable 的指针开始,其中包含指向 class 的函数指针的列表“虚拟”的方法。当 class 从“派生”时,后代通过简单地将对象的 vtable 指针设置为新的函数指针列表来“覆盖”虚拟方法。 OOP 的这个概念在 C 中并不真正存在,但它是 C++ 的基础。在C中,它必须手动实现,这就是这段代码所做的。

基本上,代码正在为分配的对象分配此内存布局:

           ------------    --------------------
void *p -> | class_t* | -> | size_t size       |
           ------------    --------------------
           | ...      |    | void (*ctor)()   |
           ------------    --------------------
                           | void (*dtor)()   |
                           --------------------
                           | void (*clone)()  |
                           --------------------
                           | void (*differ)() |
                           --------------------

完成相同赋值的另一种方法是对“class”类型使用 typedef 以便于访问,例如原始代码等同于:

typedef struct
{
    class_t *vtable;
    // other data members, if class->size > sizeof(class_t*) ...
} class_info_t;

inline void *new (const void *_class, ...)
{
    const class_t *class = _class;
    class_info_t *p = (class_info_t*) calloc(1, class->size);

    assert(p);
    p->vtable = class;
    // other data members are implicitly zeroed by calloc() ...

    ...
}

根本不使用任何 typedef 或转换,memcpy() 可以用来完成同样的事情,例如:

inline void *new (const void *_class, ...)
{
    const class_t *class = _class;
    void *p = calloc(1, class->size);

    assert(p);
    memcpy(p, &class, sizeof(class));

    ...
}

What does it mean const class_t ** type?

它是一个指向指针的指针。它表示它所指向的实际上是另一个指针。而 2nd 指向具体的 object 类型,在这种情况下,class_t

[type] ---> [2nd pointer] ---> [class_t object]

为什么*(const class_t **)p = class;

这个奇怪的结构所做的是将 class 放在“class_t object”所在的位置。现在,看看这个 new 函数应该如何使用它并不奇怪。

new 是任何自定义类型(结构)的通用构造函数。然而,对 type 有一个要求,即结构必须包含 指向 class_t 的指针作为第一个成员。这是为了启用多态性,基本上是 C++ 在引擎盖下使用指向 v-table.

的指针所做的事情

现在,如果我要定义自定义类型,我会这样做:

struct Foo {
    void *class_t;
    // whatever members I need for my type
};

如何使用?

现在,定义了类型后,还有一件事要做。请注意,new 函数作为参数接受一个指针 inline void *new (const void *_class, ...),该指针用作正在创建的函数的基础 const class_t *class = _class;。这有点意味着您需要在创建内容时传递内容 - 那么有什么意义呢?

好吧,有一个技巧可以在 header 中定义一个指向 类型 的 const 指针,可以用来构造 object 的 object类型。

在 foo header:

struct Foo {
    void *class_t;
    // whatever members I need for my type
};

static const struct class_t _Foo = { 
sizeof (struct Foo), 
// and other functions such ad ctor, dtro, etc...
}; 

const void *Foo = &_Foo;

上面定义了一个自定义类型和一个矩阵来创建该类型的所有 object。需要注意的是,它的类型是class_t,记录的是Foo的大小

最后,新 object 的 Foo 创建如下:

void *f = new(Foo);

然而 Fooclass_t 类型,但我们希望它是 FooFoo 结构的第一个元素是指向 class_t 的指针,因此为了创建这种关系,需要在 new

内部使用双指针