2012-05-03 21 views
6

Estoy aprendiendo C, principalmente por K & R, pero ahora he encontrado un tutorial de C orientado a objetos y estoy fascinado. Estoy pasando por eso, pero mis habilidades/conocimientos C pueden no estar a la altura de la tarea. Este es el tutorial: http://www.planetpdf.com/codecuts/pdfs/ooc.pdfVoid punteros a struct punteros en C

Mi pregunta proviene de observar muchas funciones diferentes en los primeros dos capítulos del pdf. Debajo está uno de ellos. (página 14 de pdf)

void delete(void * self){ 
    const struct Class ** cp = self; 

    if (self&&*cp&&(*cp)->dtor) 
       self = (*cp)->dtor(self); 
    free(self); 

} 

dtor es un puntero a la función de destructor. Pero el conocimiento de esto no es realmente necesario para mis preguntas.

  • Mi primera pregunta es, ¿por qué ** cp constant? ¿Es necesario o solo es exhaustivo para que el escritor del código no haga nada dañino por accidente?
  • En segundo lugar, ¿por qué cp es un puntero a un puntero (asterisco doble?). La clase struct se definió en la página 12 del pdf. No entiendo por qué no puede ser un solo puntero, ya que estamos lanzando el puntero al puntero de clase, parece.
  • En tercer lugar, ¿cómo se cambia un puntero vacío a un puntero de Clase (o puntero a puntero de Clase)? Creo que esta pregunta muestra mi falta de comprensión de C. Lo que imagino en mi cabeza es un puntero de vacío que ocupa una cantidad determinada de memoria, pero debe ser inferior al puntero de clase, porque una clase tiene muchas "cosas" en eso. Sé que un puntero vacío puede ser "lanzado" a otro tipo de puntero, pero no entiendo cómo, ya que puede no haber suficiente memoria para realizar esto.

Gracias de antemano

+3

@Joe Absolutamente nada de malo en eso. Las buenas habilidades de programación C son tan valiosas y difíciles de encontrar en estos días de escrituras rápidas. – jman

+0

@Joe - Supongo que significa ANSI C89, que el último libro de K & R discutió. el código en el ejemplo no es K & R C IIRC. – Flexo

+0

Estoy leyendo K & R ANSI (creo que desde 1989, como dijiste). El ejemplo del código proviene del OOC pdf –

Respuesta

2

Interesante pdf.

Mi primera pregunta es, ¿por qué ** cp constant? ¿Es necesario o simplemente ser minucioso para que el escritor del código no haga nada dañino por el accidente ?

Es necesario por lo que el escritor no hace nada por accidente, sí, y para comunicar algo acerca de la naturaleza del puntero y su utilidad para el lector del código.

En segundo lugar, ¿por qué cp es un puntero-a-un-puntero (asterisco doble?). La clase de estructura se definió en la página 12 del pdf. No entiendo por qué no puede ser un solo puntero, ya que estamos lanzando el puntero a un puntero de clase, al parecer.

Echa un vistazo a la definición de new() (pg 13) donde se crea el puntero p (el mismo puntero que se pasa como self-delete()):

void * new (const void * _class, ...) 
{ 
    const struct Class * class = _class; 
    void * p = calloc(1, class —> size); 
    * (const struct Class **) p = class; 

Por lo tanto, se asigna 'p' espacio, luego desreferenciado y asignado un valor de puntero (la dirección en la clase; esto es como desreferenciar y asignar a un puntero int, pero en lugar de un int, estamos asignando una dirección). Esto significa que lo primero en p es un puntero a su definición de clase. Sin embargo, a p se le asignó espacio para algo más que eso (también mantendrá los datos de instancia del objeto). Consideremos ahora de nuevo delete():

const struct Class ** cp = self; 
if (self&&*cp&&(*cp)->dtor) 

Cuando se eliminan las referencias cp, ya que era un puntero a un puntero, que es ahora un puntero. ¿Qué contiene un puntero? Una dirección. ¿Que direccion? El puntero a la definición de clase que está al comienzo del bloque al que apunta p.

Esto es algo inteligente, porque p no es realmente un puntero a un puntero: tiene una gran cantidad de memoria asignada que contiene los datos específicos del objeto. Sin embargo, al comienzo de ese bloque hay una dirección (la dirección de la definición de la clase), de modo que si p se desreferencia en un puntero (a través de casting o cp), tiene acceso a esa definición. Entonces, la definición de clase existe solo en un lugar, pero cada instancia de esa clase contiene una referencia a la definición. ¿Tener sentido?Sería más claro si p se escribe como una estructura como esta:

struct object { 
    struct class *class; 
    [...] 
}; 

Entonces usted podría utilizar algo como p->class->dtor() en lugar del código existente en delete(). Sin embargo, esto podría estropear y complicar la imagen más grande.

En tercer lugar, ¿cómo es un puntero nulo se cambió a un puntero de clase (o puntero-a-una-clase-puntos)? Creo que esta pregunta muestra mi falta de comprensión de C. Lo que imagino en mi cabeza es un puntero de vacío ocupando una cantidad determinada de memoria, pero debe ser menor que el puntero Clase , porque una Clase tiene una gran cantidad de "cosas" en eso.

Un puntero es como un int: tiene un tamaño pequeño y fijo para contener un valor. Ese valor es una dirección de memoria. Cuando desreferencia un puntero (a través de * o ->), lo que está accediendo es la memoria en esa dirección. Pero dado que las direcciones de memoria son todas de la misma longitud (por ejemplo, 8 bytes en un sistema de 64 bits), los punteros son del mismo tamaño independientemente del tipo. Así es como funcionaba la magia del puntero del objeto 'p'. Para volver a iterar: lo primero en el bloque de memoria p señala a es una dirección, que le permite funcionar como un puntero a un puntero, y cuando se desreferencia, se obtiene el bloque de memoria que contiene la definición de clase, que está separado de los datos de instancia en p.

0
  1. const se utiliza para causar un error de compilación si el código intenta cambiar nada dentro del objeto apuntado. Esta es una característica de seguridad cuando el programador solo intenta leer el objeto y no tiene la intención de cambiarlo.

  2. ** se utiliza porque debe ser lo que se pasó a la función. Sería un grave error de programación volver a declararlo como algo que no es.

  3. Un puntero es simplemente una dirección. En casi todas las CPU modernas, todas las direcciones son del mismo tamaño (32 bits o 64 bits). Cambiar un puntero de un tipo a otro en realidad no cambia el valor. Dice que considere lo que está en esa dirección como un diseño de datos diferente.

1
  1. En este caso, eso es sólo una medida de precaución. La función no debería estar modificando la clase (de hecho, nada debería probablemente), por lo que la conversión a const struct Class * asegura que la clase sea más difícil de cambiar inadvertidamente.

  2. No estoy muy familiarizado con la biblioteca Object-Oriented C que se utiliza aquí, pero sospecho que es un truco desagradable. El primer puntero en self es probablemente una referencia a la clase, por lo que desreferenciar self dará un puntero a la clase. En efecto, self siempre se puede tratar como struct Class **.

    Un diagrama puede ayudar aquí:

     +--------+ 
    self -> | *class | -> [Class] 
         | .... | 
         | .... | 
         +--------+ 
    
  3. Recuerde que todos los punteros son sólo aborda * El tipo de un puntero no tiene relación con el tamaño del puntero;. son todos de 32 o 64 bits de ancho, dependiendo de su sistema, por lo que puede convertir de un tipo a otro en cualquier momento. El compilador le advertirá si intenta convertir entre tipos de puntero sin un molde, pero los punteros void * siempre se pueden convertir a cualquier elemento sin un molde, ya que se usan en C para indicar un puntero "genérico".

*: Existen algunas plataformas extrañas en las que esto no es cierto, y diferentes tipos de punteros a veces son de diferentes tamaños. Sin embargo, si estás usando uno de ellos, lo sabrías. Con toda probabilidad, no lo eres.