最近我回答another question asking for questions every decent C++ programmer should be able to answer。 My 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
)?
答案 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_
存储在0x04
,y_
存储在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信息的方法,这可能会更好。