Как можно реализовать указатель, кроме хранения адреса? - PullRequest
10 голосов
/ 15 октября 2010

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

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

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

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

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

Ответы [ 6 ]

6 голосов
/ 15 октября 2010

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

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

См. http://c -faq.com / null / machexamp.html для некоторых других примеров. Я нашел ссылку на машину Symbolics Lisp интригующей.

5 голосов
/ 15 октября 2010

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

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

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

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

3 голосов
/ 15 октября 2010

Если мы знакомы с доступом к элементам массива с использованием арифметики указателей, легко понять, как объекты размещаются в памяти и как работает 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 s его базовых классов. Рассмотрим следующий пример:

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* a Frame*:

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

dynamic_cast будет переходить по ссылкам type_info из таблицы 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
2 голосов
/ 15 октября 2010

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

1 голос
/ 15 октября 2010

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

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

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

Кроме сегмента: смещение (которое, очевидно, является адресом, состоящим из двух чисел), наиболее вероятный «забавный указатель», о котором я могу подумать, - это тот, в котором некоторая информация о типе содержится в указателе. Маловероятно, что в C ++ вам захочется изрядно оптимизировать RTTI за счет уменьшения пространства, к которому вы можете обращаться, но вы никогда не знаете.

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

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

1 голос
/ 15 октября 2010

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

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

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

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

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...