除了存储地址外,如何实现指针?

时间:2010-10-15 06:11:32

标签: c++ pointers compiler-construction casting

最近我回答another question asking for questions every decent C++ programmer should be able to answerMy suggestion

Q: How does a pointer point to an object?
A: The pointer stores the address of that object.

但是user R..不同意我向Q提议的A - 他说正确答案是“它是特定于实现的”。虽然现在的实现将数字地址存储为指针,但没有理由它不能更精细

我绝对不能不同意可能是其他实现,除了为了不同意而存储地址。我真的很感兴趣那里有其他真正使用的实现。

除了在整数类型变量中存储地址外,C ++中其他实际使用的指针实现是什么?如何实现强制转换(尤其是dynamic_cast)?

6 个答案:

答案 0 :(得分:6)

在概念层面上,我同意你的观点 - 我将对象的地址定义为“在内存中定位对象所需的信息”。但是,地址看起来可能会有很大差异。

这些天的指针值通常表示为一个简单的线性地址......但是有一些架构,其中地址格式不是那么简单,或根据类型而有所不同。例如,在x86上以实模式编程(例如在DOS下),有时您必须将地址存储为段:偏移对。

有关更多示例,请参阅http://c-faq.com/null/machexamp.html。我发现对Symbolics Lisp机器的引用很有趣。

答案 1 :(得分:5)

我会以Boost.Interprocess为证人。

Boost.Interprocess中,进程间指针是映射内存区域开头的偏移量。这允许从另一个进程获取指针,映射内存区域(哪个指针地址可能与传递指针的进程中的指针地址不同)仍然可以到达同一个对象。

因此,进程间指针不表示为地址,但可以将它们解析为一个。

感谢收看: - )

答案 2 :(得分:3)

如果我们熟悉使用指针算法访问数组元素,很容易理解对象在内存中的布局方式以及dynamic_cast的工作原理。考虑以下简单类:

struct point
{
    point (int x, int y) : x_ (x), y_ (y) { }
    int x_;
    int y_;
};

point* p = new point(10, 20); 

假设p已分配给内存位置0x01。其成员变量存储在各自不同的位置,例如x_存储在0x04y_存储在0x07。将对象p可视化为指针数组更容易。 p(在我们的例子中(0x1)指向数组的开头:

0x01
+-------+-------+
|       |       |
+---+---+----+--+
    |        |
    |        |
   0x04     0x07
 +-----+   +-----+
 |  10 |   | 20  |
 +-----+   +-----+

因此访问字段的代码实际上将使用指针算法访问数组元素:

p->x_; // => **p
p->y_; // => *(*(p + 1))

如果语言支持某种自动内存管理(如GC),则可能会在场景后面的对象数组中添加其他字段。想象一下,在引用计数的帮助下收集垃圾的C ++实现。然后编译器可能会添加一个额外的字段(rc)来跟踪该计数。然后上面的数组表示变为:

0x01
+-------+-------+-------+
|       |       |       |
+--+----+---+---+----+--+
   |        |        |
   |        |        |
  0x02     0x04     0x07
+--+---+  +-----+   +-----+
|  rc  |  |  10 |   | 20  |
+------+  +-----+   +-----+

第一个单元格指向引用计数的地址。编译器将发出适当的代码来访问外部世界应该看到的p部分:

p->x_; // => *(*(p + 1))
p->y_; // => *(*(p + 2))

现在很容易理解dynamic_cast的工作原理。编译器通过向底层表示添加额外的隐藏指针来处理多态类。该指针包含另一个名为 vtable 的“数组”开头的地址,该数组又包含此类中虚函数实现的地址。但 vtable 的第一个条目是特殊的。它不指向函数地址,而是指向名为type_info的类的对象。此对象包含对象的运行时类型信息和指向其基类的type_info的指针。请考虑以下示例:

class Frame
{
public:
    virtual void render (Screen* s) = 0;
    // ....
};

class Window : public Frame
{ 
public:
    virtual void render (Screen* s)
    {
        // ...
    }
    // ....
private:
   int x_;
   int y_;
   int w_;
   int h_;
};

Window的对象将具有以下内存布局:

window object (w)
+---------+
| &vtable +------------------+
|         |                  |
+----+----+                  |
+---------+     vtable       |            Window type_info    Frame type_info
|  &x_    |     +------------+-----+      +--------------+    +----------------+
+---------+     | &type_info       +------+              +----+                |
+---------+     |                  |      |              |    |                |
|  &y_    |     +------------------+      +--------------+    +----------------+
+---------+     +------------------+
+---------+     | &Window::render()|
+---------+     +------------------+    
+---------+                     
|  &h_    |
+---------+

现在考虑当我们尝试投放Window*一个Frame*时会发生什么:

Frame* f = dynamic_cast<Frame*> (w);

dynamic_cast将跟随type_info的vtable中的w个链接,确认Frame位于其基类列表中,并将w分配给{ {1}}。如果在列表中找不到f,则Frame设置为f,表示投射失败。 vtable 提供了一种表示类0的经济方式。这是type_info仅适用于具有dynamic_cast函数的类的一个原因。从逻辑的角度来看,将virtual限制为多态类型也是有意义的。也就是说,如果对象没有虚函数,则在不知道其确切类型的情况下无法安全地操作它。

dynamic_cast的目标类型不一定是多态的。这允许我们将具体类型包装成多态类型:

dynamic_cast

答案 3 :(得分:2)

您可以使用分段指针,在内部将内存划分为固定大小的块(小)然后将其划分为段(块的大集合),也是固定大小,因此指向对象的指针可以存储为Seg :块

+-----------------------------------------------------------+
|Segment 1 (addr: 0x00)                                     |
| +-------------------------------------------------------+ |
| |Block 1|Block 2|Block 3|Block 4|Block 5|Block 6|Block 7| |
| +-------------------------------------------------------+ |
+-----------------------------------------------------------+
|Segment 2 (addr: 0xE0)                                     |
| +-------------------------------------------------------+ |
| |Block 1|Block 2|Block 3|Block 4|Block 5|Block 6|Block 7| |
| +-------------------------------------------------------+ |
+-----------------------------------------------------------+
|Segment 3 (addr: 0x1C0)                                    |
| +-------------------------------------------------------+ |
| |Block 1|Block 2|Block 3|Block 4|Block 5|Block 6|Block 7| |
| +-------------------------------------------------------+ |
+-----------------------------------------------------------+

所以说我们有指针2:5,每个段是7个块,每个块是32个字节,然后2:5可以通过((2 - 1) * (7 * 32)) + (5 * 32)转换为x86类型指针,从第一段开头 0x180

答案 4 :(得分:1)

智能指针是指针

非静态成员函数的指针可能是复杂的结构,包含有关虚函数表的信息。

Iterator是一个通用的指针

可能正确的问题应该是这样的:

Q: How does T* point to an object of type T? (T is not a type of non-static member function)
A: When you dereference value of type T*, it contains the address of that object. (In any other time it can contain anything)

答案 5 :(得分:1)

指向对象的指针确实存储(代表)C ++所称的“地址”。 3.9.2 / 3,“对象指针类型的有效值表示内存中的字节地址(1.7)或空指针(4.10)。”

我认为可以公平地说,他们“存储”地址,只是这样说并不能传达太多。这只是说明指针的另一种方式。它们也可以存储其他信息,并且可以通过引用其他地方的其他结构来存储实际的物理/虚拟数字地址,但就C ++语义而言,指针变量包含地址。

Abyx提出了只有对象和函数指针代表地址的问题。指向成员的指针不一定代表地址。但C ++标准明确指出标准中的“指针”一词不应被视为包含指向成员的指针。所以你可能不会相信。

除了segment:offset(显然是一个由两个数字组成的地址)之外,我能想到的最合理的“搞笑指针”将是指针中包含某些类型信息的指针。在C ++中,您不太可能以减少可以解决的空间为代价来恶魔般地优化RTTI,但您永远不会知道。

另一种可能性是,如果你实现了一个垃圾收集的C ++,那么每个指针都可以存储关于它是指向堆栈还是堆的信息,也许你可以偷偷摸摸一些信息来帮助准确和保守的标记。 / p>

但是,我没有遇到任何人用C ++中的指针做任何事情,所以我不能保证它们是真正的用途。还有其他存储类型和GC信息的方法,这可能会更好。