2010-10-15 10 views
10

Recientemente he respondido another question asking for questions every decent C++ programmer should be able to answer. My suggestion fue¿Cómo se puede implementar un puntero excepto almacenar una dirección?

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

pero user R.. no está de acuerdo con la A propongo a la Q - dice que La respuesta correcta sería "que es específica de la implementación". Si bien las implementaciones actuales almacenan direcciones numéricas como punteros, no hay ninguna razón para que no sea algo mucho más elaborado.

Definitivamente no puedo estar en desacuerdo con que podría ser otras implementaciones, excepto el almacenamiento de una dirección por el simple hecho de estar en desacuerdo. Estoy realmente interesado en qué otras implementaciones realmente usadas existen.

¿Cuáles son otras implementaciones realmente utilizadas de punteros en C++ excepto que se almacena una dirección en una variable de tipo entero? ¿Cómo se implementa el casting (especialmente dynamic_cast)?

+1

Siempre he querido una computadora que utilizara el direccionamiento de coma flotante ... –

+1

Todo es solo un montón de bits. (Ve a tomar tus dispositivos de computación de n estados cuánticos en otro lugar ...) –

+0

No puedo pensar por qué ese conocimiento es importante, siempre y cuando me dé lo que quiero y me comporté de una manera conforme. ¿Me estoy perdiendo algo? Un ejemplo es el "puntero al miembro" que se representa de una manera específica de implementación – Chubsdad

Respuesta

6

En un nivel conceptual, estoy de acuerdo con usted: defino la dirección de un objeto como "la información necesaria para ubicar el objeto en la memoria". Sin embargo, la apariencia de la dirección puede variar bastante.

Un valor de puntero en estos días se suele representar como una dirección simple y lineal ... pero ha habido arquitecturas donde el formato de dirección no es tan simple o varía según el tipo. Por ejemplo, la programación en modo real en un x86 (por ejemplo, en DOS), a veces tiene que almacenar la dirección como un segmento: par de desplazamiento.

Ver http://c-faq.com/null/machexamp.html para algunos ejemplos más. Encontré la referencia a la máquina Symbolics Lisp intrigante.

+0

Me recuerda a los indicadores lejanos cuando realicé la programación C en modo real hace un par de años. Buenos tiempos ... El direccionamiento lineal es muy aburrido :) –

+0

En realidad, el direccionamiento offset de segmento también se usó en OS/2 de 16 bits (versiones 1.x). Este sistema operativo se basaba en Intel 80286, que soportaba el "modo protegido" con direccionamiento físico de 24 bits y 16:16 (selector: desplazamiento) de direccionamiento lógico donde la parte del selector apuntaba a los descriptores de segmento, indirectando a las direcciones físicas. – LaszloG

1

punteros inteligentes son punteros

Punteros a funciones miembro no estáticos pueden ser estructuras complejas, que contienen información acerca de las tablas funciones virtuales.

Iterator es un puntero genérico.

pregunta probablemente es correcto debe ser similar:

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) 
+0

El puntero inteligente generalmente almacena un puntero habitual de todos modos. ¿Podrían proporcionar más detalles sobre cómo los punteros a los miembros contienen información sobre los vtables? – sharptooth

+0

Los punteros inteligentes son solo una clase que envuelve un puntero estándar. Iteradores son un typedef. Ambos terminan siendo una dirección almacenada, que tiene diferentes usos y efectos de tiempo de ejecución. –

+0

@sharptooth Sí, los punteros inteligentes contienen un valor de tipo T *, pero no todos son direcciones de memoria. Los diferentes compiladores usan diferentes técnicas para administrar funciones virtuales. No hay una respuesta común, algunos compiladores no usan vtables en absoluto. Lea la documentación de su compilador. – Abyx

5

yo llamaría Boost.Interprocess como testigo.

En Boost.Interprocess, los punteros entre procesos son desplazamientos desde el principio del área de memoria asignada. Esto permite obtener el puntero de otro proceso, asignar el área de memoria (qué dirección del puntero puede ser diferente de la del proceso que pasó el puntero) y seguir llegando al mismo objeto.

Por lo tanto, los punteros entre procesos no se representan como direcciones, pero se pueden resolver como uno solo.

Gracias por mirar :-)

+0

¡Una sincera bienvenida a [la liga de contribuidores extraordinarios de C++] (http://stackoverflow.com/badges/49/c?userid=147192)! :-) –

+0

@James McNellis: ¿Esto significa que estoy condenado al fracaso: x? –

2

Puede utilizar punteros de segmentación, baiscally que dividieron la memoria en bloques de un tamaño fijo (pequeño) y luego dividir en segmentos que (grandes colecciones de bloques), tamaño fijo también, por lo tanto, un puntero a un objeto se puede almacenar como Seg: Block.

+-----------------------------------------------------------+ 
|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| | 
| +-------------------------------------------------------+ | 
+-----------------------------------------------------------+ 

por lo que dicen que tenemos el puntero 2:5, cada segmento es de 7 bloques, cada bloque es de 32 bytes, a continuación, 2:5 se puede traducir en un puntero de tipo x86 haciendo ((2 - 1) * (7 * 32)) + (5 * 32), que yeilds 0x180 desde el inicio de la primer segmento

3

Si estamos familiarizados con el acceso a los elementos de la matriz mediante la aritmética del puntero, es fácil entender cómo funcionan los objetos en la memoria y cómo funciona dynamic_cast. Considere la siguiente clase simple:

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

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

Supongamos que p se asigna a la posición de memoria 0x01. Sus variables miembro se almacenan en sus propias ubicaciones dispares, digamos x_ se almacena en 0x04 y y_ en 0x07. Es más fácil visualizar el objeto p como una matriz de punteros. p (en nuestro caso (0x1) señala el comienzo de la matriz:

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

Así código para acceder a los campos esencialmente se convertirá en el acceso elementos de la matriz utilizando la aritmética de punteros:

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

Si el soporte de idiomas Algún tipo de gestión de memoria automática, como GC, se pueden agregar campos adicionales al arreglo de objetos detrás de la escena. Imagine una implementación C++ que recolecta basura con la ayuda del recuento de referencias. Entonces el compilador podría agregar un campo adicional (rc) para mantener el tra ck de ese conteo. La representación de matriz anterior se convierte en:

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

La primera celda apunta a la dirección del recuento de referencias. El compilador emitirá código apropiado para acceder a las porciones de p que deben ser visibles para el mundo exterior:

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

Ahora es fácil entender cómo dynamic_cast obras. El compilador trata con clases polimórficas al agregar un puntero oculto adicional a la representación subyacente. Este puntero contiene la dirección del comienzo de otra "matriz" llamada vtable, que a su vez contiene las direcciones de las implementaciones de funciones virtuales en esta clase. Pero la primera entrada de vtable es especial. No apunta a una dirección de función sino a un objeto de una clase llamada type_info. Este objeto contiene la información del tipo de tiempo de ejecución del objeto y los punteros a type_info s de sus clases base.Consideremos el siguiente ejemplo:

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_; 
}; 

Un objeto de Window tendrá la siguiente distribución de memoria:

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

Consideremos ahora qué va a pasar cuando tratamos de echar una Window* un Frame*:

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

dynamic_cast seguirá los enlaces type_info del vtable de w, confirma que Frame está en su lista de clases base y asigne w a f. Si no puede encontrar Frame en la lista, f se establece en 0 indicando que la fundición falló. El vtable proporciona una forma económica de representar el type_info de una clase. Esta es una razón por la cual dynamic_cast funciona solo para clases con funciones virtual. Restringir dynamic_cast a tipos polimórficos también tiene sentido desde un punto de vista lógico. Esto es, si un objeto no tiene funciones virtuales, no se puede manipular de forma segura sin conocer su tipo exacto.

El tipo de destino de dynamic_cast no tiene que ser polimórfico. Esto nos permite envolver un tipo concreto en un tipo polimórfico:

// 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 
1

punteros a objetos hacen tienda (representaciones de) lo que C++ llama "direcciones". 3.9.2/3, "Un valor válido de un tipo de puntero a objeto representa la dirección de un byte en la memoria (1.7) o un puntero nulo (4.10)".

Creo que es justo decir, por lo tanto, que "almacenan" direcciones, es solo que decir eso no transmite demasiado. Es solo otra forma de decir qué punteros son. También pueden almacenar otra información, y pueden almacenar la dirección numérica física/virtual real por referencia a alguna otra estructura en otro lugar, pero en términos de semántica de C++, una variable de puntero contiene una dirección.

Abyx plantea el problema de que solo los punteros de objetos y funciones representan las direcciones. Los punteros a miembros no representan necesariamente una dirección, como tal. Pero el estándar C++ dice específicamente que la palabra "punteros" en el estándar no debe tomarse para incluir punteros a miembros. Entonces no puedes contar eso.

Aparte del segmento: desplazamiento (que obviamente es una dirección que consta de dos números), el "puntero divertido" más plausible que puedo pensar sería uno en el que algún tipo de información está contenida en el puntero. Es poco probable en C++ que desee optimizar diabólicamente RTTI a costa de reducir el espacio que puede abordar, pero nunca se sabe.

Otra posibilidad es que si estaba implementando un C++ recogido de basura, entonces cada puntero podría almacenar información acerca de si apunta a apilar o acumular, y quizás podría colarse algo de información para ayudar con el marcado preciso versus conservador.

No he encontrado a nadie que haga cualquiera de esas cosas con punteros en C++, así que no puedo garantizar que sean usos reales. Hay otras maneras de almacenar información de tipo y GC, que bien podría ser mejor.

Cuestiones relacionadas