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

标签 c class oop double-pointer

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

#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_t 的指针,而是更多“公共(public)”方法),则整个类不是类型 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() 的调用用于分配未指定“类”类型的 1 个元素的数组,其中该类型的第一个成员预计是 class_t * 指针(因此 class->size 必须至少为 sizeof(class_t*),但可以更高)。可能会使用 calloc() 来代替 malloc(),这样由 class->size 表示的任何其他数据成员都将为零 -初始化,否则需要显式的 memset()

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

可以使用双指针访问数组。取消引用这样的指针可以得到数组中第一个元素的地址。在本例中,它也恰好与 class_t* 成员的地址相同。

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

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

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

Another way to accomplish the same assignment would be to use a typedef for the "class" type for easier access, eg the original code is equivalent to this:

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() ...

    ...
}

根本不使用任何类型定义或转换,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));

    ...
}

关于c - 为什么在c中将指针转换为双指针?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63514129/

相关文章:

c - OpenSSL 使用 EVP 与算法 API 进行对称加密

PHP:将类变量声明为 stdClass 对象

javascript - 我可以在JS中获取一个对象,但不能获取它的属性

java - 将业务逻辑放在监听器/执行器类中是一种不好的做法吗?

c - 如何在C中取一行字符串

c - 如何使用 Perl 将 C 注释从一行的开头移动到结尾?

c - 错误:../../mono/io-layer/handles-private.h:362

javascript - 在 javascript 类中设置回调函数

java - 将参数传递给对象数组

arrays - 如何将数组/列表添加到对象