Punteros vacíos a punteros de estructura en C

Estoy aprendiendo C, principalmente por K&R, pero ahora encontré un tutorial en pdf de C orientado a objetos y estoy fascinado. Lo estoy pasando, pero es posible que mis habilidades/conocimientos de C no estén a la altura. Este es el tutorial: http://www.planetpdf.com/codecuts/pdfs/ooc.pdf

Mi pregunta proviene de mirar muchas funciones diferentes en los primeros dos capítulos del pdf. A continuación se muestra uno de ellos. (página 14 del 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 de función destructor. Pero el conocimiento de esto no es realmente necesario para mis preguntas.

  • Mi primera pregunta es, ¿por qué **cp es constante? ¿Es necesario o simplemente es minucioso para que el escritor del código no haga nada dañino por accidente?
  • En segundo lugar, ¿por qué cp es un puntero a otro puntero (¿doble asterisco?). 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 parece que estamos lanzando el autopuntero a un puntero de clase.
  • En tercer lugar, ¿cómo se cambia un puntero vacío a un puntero de clase (o puntero a un puntero de clase)? Creo que esta pregunta muestra más mi falta de comprensión de C. Lo que imagino en mi cabeza es un puntero vacío que ocupa una cantidad determinada de memoria, pero debe ser menor que el puntero de Clase, porque una Clase tiene muchas "cosas". en eso. Sé que un puntero vacío se puede "lanzar" a otro tipo de puntero, pero no entiendo cómo, ya que es posible que no haya suficiente memoria para realizar esto.

Gracias por adelantado

preguntado el 03 de mayo de 12 a las 18:05

@ Joe Absolutamente nada de malo en eso. Las buenas habilidades de programación en C son muy valiosas y difíciles de encontrar en estos días de secuencias de comandos rápidas. -

@Joe: supongo que significa ANSI C89, que se discutió en el libro más nuevo de K&R. el código del ejemplo no es K&R C IIRC. -

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

3 Respuestas

pdf interesante.

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

Es necesario para que el escritor no haga nada por accidente, sí, y para comunicar algo sobre la naturaleza del puntero y su uso al lector del código.

En segundo lugar, ¿por qué cp es un puntero a otro puntero (¿doble asterisco?). 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 parece que estamos lanzando el autopuntero a un puntero de clase.

Eche un vistazo a la definición de new() (pág. 13) donde el puntero p se crea (el mismo puntero que se pasa como self a delete()):

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

Entonces, a 'p' se le asigna espacio, luego se le quita la referencia y se le asigna un valor de puntero (la dirección en la clase; esto es como quitar la referencia 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 contendrá los datos de la instancia del objeto). Ahora considere delete() nuevo:

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

Cuando se elimina la referencia a cp, dado que era un puntero a un puntero, ahora es un puntero. ¿Qué contiene un puntero? Una dirección. ¿Que direccion? El puntero a la definición de clase. eso está al principio del bloque señalado por p.

Esto es bastante inteligente, porque p no es realmente un puntero a un puntero: tiene una porción más grande de memoria asignada que contiene los datos del objeto específico. Sin embargo, al principio de ese bloque hay una dirección (la dirección de la definición de la clase), por lo que si se elimina la referencia de p en un puntero (mediante conversión 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 escribiera como una estructura como esta:

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

Entonces podrías usar algo como p->class->dtor() en lugar del código existente en delete(). Sin embargo, esto estropearía y complicaría el panorama general.

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

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

contestado el 03 de mayo de 12 a las 19:05

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

  2. No estoy muy familiarizado con la biblioteca C orientada a objetos que se usa 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 puede ser tratado como un struct Class **.

    Un diagrama puede ayudar aquí:

            +--------+
    self -> | *class | -> [Class]
            |  ....  |
            |  ....  |
            +--------+
    
  3. Recuerde que todos los punteros son solo direcciones.* El tipo de puntero no tiene relación con el tamaño del puntero; todos tienen 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 conversión, pero void * los punteros siempre se pueden convertir en cualquier cosa sin conversión, ya que se usan en todo C para indicar un puntero "genérico".

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

contestado el 03 de mayo de 12 a las 19:05

  1. const se usa para causar un error de compilación si el código intenta cambiar algo dentro del objeto señalado. Esta es una función de seguridad cuando el programador solo tiene la intención de leer el objeto y no tiene la intención de cambiarlo.

  2. ** se usa porque eso 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 tienen el mismo tamaño (32 bits o 64 bits). Cambiar un puntero de un tipo a otro en realidad no cambia el valor. Dice considerar lo que está en esa dirección como un diseño diferente de datos.

contestado el 03 de mayo de 12 a las 19:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.