【问题标题】:Why to cast pointer to double pointer in c?为什么在c中将指针转换为双指针?
【发布时间】:2020-12-10 07:47:46
【问题描述】:

我正在阅读 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 的指针,还有更多“公共”方法),则整个类不是class_t 类型的数组。那么为什么我要将一个 void 指针转换为双指针并立即取消引用它呢?应该怎么理解?

来自关于该声明的书: * (const struct Class **) p = class;

p 指向对象的新内存区域的开始。我们 强制转换 p 将对象的开头视为 指向 struct class_t 的指针并将参数类设置为 这个指针。

【问题讨论】:

  • 在 C 代码中看到 class 是一个标志,因为这将是一场疯狂的旅程。
  • @tadman 但这就是重点。我从我在 c 中学习 OOP 的符号开始
  • 考虑到 Objective-C 和 C++ 已经存在,更不用说其他鲜为人知的实验,我想知道为什么
  • @tadman 因为例如内核使用 OOP 原则
  • 所以双指针解引用一次回到指针然后放置类?添加 varg 黑魔法以获得乐趣,您将 va_list 堆栈变量的地址存储到类中然后让它超出范围?会出什么问题?希望 ctor 深拷贝。

标签: c class oop double-pointer


【解决方案1】:

const class_t ** type?是什么意思

它是一个指向指针的指针。它表示无论它指向什么实际上都是另一个指针。而第二个指向类型的具体对象,在这种情况下,class_t

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

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

这个奇怪的结构所做的是将class 放置在“class_t 对象”所在的位置。现在,看看这个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; 的基础。这有点意味着你需要在创建东西时传递一些东西——那么有什么意义呢?

嗯,有一个技巧可以在它的标头中定义一个指向 type 的 const 指针,该指针可用于构造这种类型的对象。

在 foo 标头中:

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;

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

最后,Foo的新对象创建如下:

void *f = new(Foo);

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

【讨论】:

    【解决方案2】:

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

    奇怪的 cast+dereference 只是为了让代码可以将输入 class 指针直接存储到该分配对象的第一个 class_t* 成员中。

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

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

    基本上,代码是为分配的对象分配这个内存布局:

    ------------ -------- 无效 *p -> |类_t * | -> | size_t 尺寸 | ------------ -------- | ... | |无效 (*ctor)() | ------------ -------- |无效 (*dtor)() | -------------------- |无效 (*克隆)() | -------------------- |无效(*不同)()| --------------------

    完成相同任务的另一种方法是使用 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() ...
    
        ...
    }
    

    完全不使用任何类型定义或强制转换,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));
    
        ...
    }
    

    【讨论】:

    • 但是为什么它直接是(class_t *)p = _class,当pvoid* 时,我可以将它转换为我想要的任何指针类型。为什么我必须使用指向class_t 的双指针,因此至少要取消引用一次?
    • @milanHrabos 因为它指向内存中的一个对象,并且该对象有各种字段,而这些字段中的第一个是指针,即vtable指针,您要初始化该指针。
    • "为什么直接是 (class_t *)p = _class"" - 因为 1) 在语法方面不起作用,以及 2) 无法完成所需的任务将class复制到分配内存的前面。我已经更新了我的答案来解释这个。
    猜你喜欢
    • 2021-01-08
    • 1970-01-01
    • 2013-02-20
    • 1970-01-01
    • 2021-07-22
    • 1970-01-01
    • 1970-01-01
    • 2016-08-24
    • 2021-09-20
    相关资源
    最近更新 更多