C语言中的空指针与结构体指针

7
我正在学习C语言,主要是通过K&R书籍,但现在我发现了一份面向对象的C语言PDF教程,非常着迷。我正在学习它,但我的C语言技能和知识可能还不够。 这是教程链接:http://www.planetpdf.com/codecuts/pdfs/ooc.pdf 我的问题来自于阅读PDF的前几章中的许多不同函数之一。以下是其中之一(PDF第14页)。
void delete(void * self){
     const struct Class ** cp = self;

     if (self&&*cp&&(*cp)->dtor)
                self = (*cp)->dtor(self);
     free(self);

 }

dtor是一个析构函数指针。但了解这并不是我问题的必要条件。

  • 我的第一个问题是,为什么**cp是常量?这是必要的还是只是为了防止代码编写人员意外造成破坏?
  • 其次,为什么cp是指向指针的指针(双星号)?结构体类在pdf的第12页定义。我不明白为什么它不能是单个指针,因为我们将self指针转换为Class指针。
  • 第三,如何将void指针更改为Class指针(或指向Class指针的指针)?我认为这个问题最能显示我对C的理解不足。我脑海中想象的是,void指针占用一定数量的内存,但它必须小于Class指针,因为Class包含很多“东西”。我知道void指针可以“强制转换”为另一种类型的指针,但我不明白如何操作,因为可能没有足够的内存来执行此操作。

提前感谢。


3
@Joe 绝对没有任何问题。在这个脚本快速发展的时代,良好的C编程技能非常珍贵且难以找到。 - jman
@Joe - 我认为这意味着 ANSI C89,新版的 K&R 书中讨论过。如果我没记错,示例中的代码不是 K&R C。 - Flexo
我正在阅读 K&R ANSI(我认为是你所说的 1989 年版)。代码示例来自 OOC PDF。 - user485498
3个回答

3

有趣的pdf。

我的第一个问题是,为什么**cp是常数?这是必要的还是只是为了防止代码编写者意外地做出任何有害行为?

是的,这是为了防止编写者意外地做出任何事情,并向代码的读者传达指针及其使用性质的某些信息。

其次,为什么cp是指向指针的指针(双星号)?结构类在pdf的第12页上定义。我不明白为什么它不能是单个指针,因为我们似乎将自指针强制转换为类指针。

看一下new()的定义(第13页),其中创建指针p(作为self传递给delete()的同一指针):

void * new (const void * _class, ...)
{
    const struct Class * class = _class;
    void * p = calloc(1, class —> size);
    * (const struct Class **) p = class;

因此,'p'被分配空间,然后被取消引用并赋值一个指针值(类中的地址;这就像取消引用和赋值给int指针一样,但我们不是给int赋值,而是给一个地址赋值)。这意味着p中的第一件事是指向其类定义的指针。然而,p被分配的空间不仅仅是用于此目的(它还将保存对象的实例数据)。现在再考虑一下delete():
 const struct Class ** cp = self;
 if (self&&*cp&&(*cp)->dtor)

当cp被引用时,由于它是指向指针的指针,所以现在它是一个指针。指针包含什么内容?一个地址。是什么地址?指向p所指向的块开头处的类定义的指针。
这有点聪明,因为p不是真正的指向指针--它分配了更大的内存块,其中包含特定的对象数据。然而,在该块的开头处有一个地址(类定义的地址),因此,如果p通过转换或cp被解除引用成指针,您就可以访问该定义。因此,类定义只存在于一个地方,但该类的每个实例都包含对该定义的引用。听起来有道理吗?如果p被声明为struct,将更清晰:
struct object {
  struct class *class;
    [...]
};

然后,您可以使用像p->class->dtor()这样的东西来代替delete()中的现有代码。但是,这会破坏并复杂化更大的图像。
第三个问题是,如何将void指针更改为Class指针(或指向Class指针的指针)?我认为这个问题最能显示出我对C的理解不足。我脑海中想象的是,void指针占用一定量的内存,但它必须比Class指针少,因为Class中有很多“东西”。
指针就像int一样--它具有小的、固定的大小来保存一个值。该值是一个内存地址。当您通过*->解除引用指针时,您访问的是该地址处的内存。但是,由于内存地址都具有相同的长度(例如,在64位系统上为8字节),指针本身的大小与类型无关。这就是对象指针“p”的魔法之处。再次强调:指针

所指向的内存块中的第一件事是一个地址,这使它可以作为指向指针的指针,当对其进行解引用时,您会得到包含类定义的内存块,该内存块与p中的实例数据分开。


2
  1. In this case, that's just a precaution. The function shouldn't be modifying the class (in fact, nothing should probably), so casting to const struct Class * makes sure that the class is more difficult to inadvertently change.

  2. I'm not super-familiar with the Object-Oriented C library being used here, but I suspect this is a nasty trick. The first pointer in self is probably a reference to the class, so dereferencing self will give a pointer to the class. In effect, self can always be treated as a struct Class **.

    A diagram may help here:

            +--------+
    self -> | *class | -> [Class]
            |  ....  |
            |  ....  |
            +--------+
    
  3. Remember that all pointers are just addresses.* The type of a pointer has no bearing on the size of the pointer; they're all 32 or 64 bits wide, depending on your system, so you can convert from one type to another at any time. The compiler will warn you if you try to convert between types of pointer without a cast, but void * pointers can always be converted to anything without a cast, as they're used throughout C to indicate a "generic" pointer.

*: 有一些奇怪的平台不符合这个规则,不同类型的指针实际上有时是不同的大小。但如果您正在使用其中之一,那么您应该已经知道了。很可能您没有遇到这种情况。


0
  1. const用于在代码试图更改指向的对象内部任何内容时导致编译错误。当程序员仅打算读取对象而不打算更改它时,这是一项安全功能。

  2. **被使用是因为必须将其传递给函数。重新声明为其它类型将是一个严重的编程错误。

  3. 指针只是一个地址。在几乎所有现代CPU上,所有地址的大小都相同(32位或64位)。将指针从一种类型更改为另一种类型实际上并不会更改值。它表示将该地址处的内容视为不同的数据布局。


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