Как реализовать указатель, кроме сохранения адреса?

Недавно я ответил на еще один вопрос, задающий вопросы, на которые должен отвечать любой достойный программист на С++. Мое предложение было

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

но пользователь R.. не согласен с A, я предлагаю Q - он говорит, что правильным ответом будет "он специфичен для реализации". В то время как современные реализации хранят числовые адреса в качестве указателей, нет причин, по которым он не может быть более сложным.

Определенно, я не могу не согласиться с тем, что могут быть другие реализации, кроме хранения адреса только ради несогласия. Мне действительно интересно, что есть другие реально используемые реализации.

Каковы другие фактически используемые реализации указателей в С++, кроме сохранения адреса в переменной целочисленного типа? Как реализовано литье (особенно dynamic_cast)?

Ответ 1

На концептуальном уровне я согласен с вами - я определяю адрес объекта как "информацию, необходимую для поиска объекта в памяти". Однако, как выглядит адрес, он может немного отличаться.

Значение указателя в эти дни обычно представлено как простой, линейный адрес... но были архитектуры, где формат адреса не так прост или варьируется в зависимости от типа. Например, программирование в реальном режиме на x86 (например, под DOS), вам иногда приходится хранить адрес в виде пары сегмент: смещение.

Подробнее см. http://c-faq.com/null/machexamp.html. Я нашел ссылку на машинное приложение Symbolics Lisp.

Ответ 2

Я бы назвал Boost.Interprocess свидетелем.

В Boost.Interprocess указатели интерпроцессов являются смещениями от начала отображаемой области памяти. Это позволяет получить указатель из другого процесса, отобразить область памяти (какой адрес указателя может отличаться от указателя в процессе, который прошел указатель) и все равно перейти к одному и тому же объекту.

Следовательно, указатели межпроцессорности не представлены как адреса, но они могут быть разрешены как один.

Спасибо за просмотр: -)

Ответ 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, дополнительные поля могут быть добавлены в массив объектов за сценой. Представьте себе реализацию на С++, которая собирает мусор с помощью подсчета ссылок. Затем компилятор может добавить дополнительное поле (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_    |
+---------+

Теперь рассмотрим, что произойдет, когда мы попытаемся применить a Window* a Frame*:

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

dynamic_cast будет следовать за ссылками type_info из таблицы vtable w, подтверждает, что Frame находится в списке базовых классов и присваивает w f. Если он не может найти Frame в списке, f установлен на 0, указывающий, что кастинг завершился неудачно. Vtable предоставляет экономичный способ представления type_info класса. Это одна из причин, по которой dynamic_cast работает только для классов с функциями virtual. Ограничение dynamic_cast на полиморфные типы также имеет смысл с логической точки зрения. Это означает, что если объект не имеет виртуальных функций, его нельзя безопасно манипулировать без знания его точного типа.

Целевой тип dynamic_cast не обязательно должен быть полиморфным. Это позволяет нам обернуть конкретный тип полиморфным типом:

// no virtual functions
class A 
{
};

class B
{
public:
    virtual void f() = 0;
};

class C : public A, public B
{
    virtual void f() { }
};


C* c = new C;
A* a = dynamic_cast<A*>(c); // OK

Ответ 4

Вы можете использовать указатели сегментации, по сути, вы разделили память на блоки фиксированного размера (малые), затем разделите их на сегменты (большие коллекции блоков), фиксированный размер тоже, таким образом, указатель на объект может быть сохранен как 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 может быть переведен в указатель типа x86, выполнив ((2 - 1) * (7 * 32)) + (5 * 32), который yeilds 0x180 с начала первого сегмента

Ответ 5

Умные указатели - это указатели

Указатели на нестатические функции-члены могут быть сложными структурами, содержащими информацию о таблицах виртуальных функций.

Итератор - общий указатель.

Вероятно, правильный вопрос должен выглядеть так:

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)

Ответ 6

Указатели на объекты сохраняют (представления) то, что С++ вызывает "адреса". 3.9.2/3: "Допустимое значение типа указателя объекта представляет собой либо адрес байта в памяти (1.7), либо нулевой указатель (4.10)."

Я думаю, справедливо сказать, что они "хранят" адреса, а просто говорят, что это не передает много. Это просто еще один способ сказать, что такое указатели. Они могут хранить и другую информацию, и они могут хранить фактический физический/виртуальный числовой адрес по ссылке на какую-либо другую структуру в другом месте, но в терминах семантики С++ переменная указателя содержит адрес.

Abyx поднимает вопрос, что только адреса объектов и функций представляют адреса. Указатели на член не обязательно представляют собой адрес, как таковой. Но в стандарте С++ конкретно говорится, что слово "указатели" в стандарте не следует принимать, чтобы включить указатели на член. Поэтому вы можете не считать этого.

Помимо сегмента: offset (который, очевидно, является адресом, состоящим из двух чисел), наиболее правдоподобным "забавным указателем", о котором я могу думать, будет тот, в котором какая-либо информация о типе содержится в указателе. В С++ маловероятно, что вы хотите искусно оптимизировать RTTI за счет сокращения пространства, которое вы можете решить, но вы никогда не знаете.

Другая возможность заключается в том, что если вы выполняли сборку С++ с мусором, каждый указатель мог бы хранить информацию о том, указывает ли она на стек или кучу, и, возможно, вы могли бы прокрасться в некоторую информацию, чтобы помочь с точными или консервативными обозначениями.

Я не сталкивался с тем, что кто-либо делал одну из этих вещей с указателями на С++, поэтому я не могу ручаться за то, что они действительно используются. Существуют и другие способы хранения информации о типе и GC, что может быть лучше.