2008-11-01 27 views
12

Esto va a sonar como una pregunta tonta, pero todavía estoy aprendiendo C, así que por favor tengan paciencia conmigo. :)Pasar punteros/referencias a estructuras en funciones

Estoy trabajando en el capítulo 6 de K & R (structs), y hasta ahora a través del libro han tenido un gran éxito. Decidí trabajar con estructuras bastante pesadas, y por lo tanto trabajé mucho al comienzo del capítulo con los ejemplos de punto y rect. Una de las cosas que quería probar era cambiar la función canonrect (2da Edición, p 131) a través de punteros, y por lo tanto devolver void.

Tengo esto funcionando, pero tuve un problema con el que esperaba que ustedes pudieran ayudarme. Quería canonRect crear un objeto rectángulo temporal, realizar sus cambios, luego reasignar el puntero al rectángulo temporal, simplificando así el código.

Sin embargo, si hago eso, el rect no cambia. En cambio, me encuentro repoblando manualmente los campos de rect que me dieron, lo cual funciona.

El código sigue:

#include <stdio.h> 

#define min(a, b) ((a) < (b) ? (a) : (b)) 
#define max(a, b) ((a) > (b) ? (a) : (b)) 

struct point { 
    int x; 
    int y; 
}; 

struct rect { 
    struct point lowerLeft; 
    struct point upperRight; 
}; 

// canonicalize coordinates of rectangle 
void canonRect(struct rect *r); 

int main(void) { 
    struct point p1, p2; 
    struct rect r; 

    p1.x = 10; 
    p1.y = 10; 
    p2.x = 20; 
    p2.y = 40; 
    r.lowerLeft = p2; // note that I'm inverting my points intentionally 
    r.upperRight = p1; 

    printf("Rectangle, lower left: %d, %d; upper right: %d %d\n\n", 
     r.lowerLeft.x, r.lowerLeft.y, r.upperRight.x, r.upperRight.y); 

    // can't pass a pointer, only a reference. 
    // (Passing pointers results in illegal indirection compile time errors) 
    canonRect(&r); 
    printf("Rectangle, lower left: %d, %d; upper right: %d %d\n\n", 
     r.lowerLeft.x, r.lowerLeft.y, r.upperRight.x, r.upperRight.y);  
} 

void canonRect(struct rect *r) { 
    struct rect temp; 
    temp.lowerLeft.x = min(r->lowerLeft.x, r->upperRight.x); 
    temp.lowerLeft.y = min(r->lowerLeft.y, r->upperRight.y); 
    temp.upperRight.x = max(r->lowerLeft.x, r->upperRight.x); 
    temp.upperRight.y = max(r->lowerLeft.y, r->upperRight.y); 

    r = &temp; // doesn't work; my passed-in rect remains the same 

    // I wind up doing the following instead, to reassign all 
    // the members of my passed-in rect 
    //r->lowerLeft = temp.lowerLeft; 
    //r->upperRight = temp.upperRight; 
} 

Así que aquí están las preguntas:

  1. ¿Por qué r = &temp; no funciona? (I piensa esto es porque paso en una referencia en lugar de un puntero; ¿estoy en lo cierto al pensar que las referencias no son modificables pero los punteros son?)
  2. ¿Por qué podría obtener un error de tiempo de compilación indirecto ilegal si lo intento pasar en un puntero al canonRect? (. IE, si tuviera canonRect(*r); en main())

Sospecho que ya sé la respuesta a # 1, # 2, pero me deja perplejo - pensé que era legal para pasar punteros alrededor.

De todos modos ... por favor perdone el C newb.

Respuesta

18

Creo que lo que quiere hacer es la siguiente:

void canonRect(struct rect *r) { 
    struct rect temp; 
    temp.lowerLeft.x = min(r->lowerLeft.x, r->upperRight.x); 
    temp.lowerLeft.y = min(r->lowerLeft.y, r->upperRight.y); 
    temp.upperRight.x = max(r->lowerLeft.x, r->upperRight.x); 
    temp.upperRight.y = max(r->lowerLeft.y, r->upperRight.y); 

    *r = temp; 
} 

En el código anterior está configurando * r que es del tipo rect a la temperatura que es del tipo rect.

Re 1: Si quiere cambiar lo que apunta, necesita usar un puntero a un puntero. Si eso es realmente lo que quieres (ver más arriba, no es realmente lo que quieres) entonces deberías asegurarte de apuntar a algo en el montón. Si lo apunta a algo que no se creó con 'nuevo' o malloc, quedará fuera del alcance y señalará la memoria que ya no se usa para esa variable.

¿Por qué su código no funciona con r = & temp?

Porque r es de rect * tipo. Eso significa que r es una variable que contiene una dirección de memoria cuya memoria contiene un rect. Si cambia lo que está señalando r, está bien, pero eso no cambia la variable pasada.

Re 2: * cuando no se usa en una declaración de tipo es el operador unario de desreferencia. Esto significa que buscará lo que está dentro de la dirección del puntero. Entonces al pasar * r no estás pasando un puntero en absoluto.En vista desde que r no es un puntero, esa es una sintaxis inválida.

+0

Ehhh ... Debo tener problemas cerebrales hoy. Pasar un puntero a un puntero sería canonRect (** r) ;, no? Los mismos errores indirectos ilegales. –

+0

Wow; gran edición! ¡Gracias! –

+0

Acabo de probarlo; Éste es el indicado. Gracias por la minuciosa explicación. –

12

También vale la pena señalar que su variable 'struct rec temp' va a quedar fuera del alcance tan pronto como el método canonRect termine y estará apuntando a la memoria no válida.

+0

Ese es un buen punto en el que no había pensado. ¡Buena atrapada! ¡Gracias! –

4

Parece que está confundiendo el operador 'dereference' (*) con el operador 'address of' (&).

Cuando escribe &r, obtiene la dirección de r y devuelve un puntero a r (un puntero es solo una dirección de memoria de una variable). Entonces realmente está pasando un puntero a la función.

Cuando escribe *r, está intentando desreferenciar r. Si r es un puntero, devolverá el valor al que r apunta. Pero r no es un puntero, es un rect, por lo que obtendrá un error.

Para hacer las cosas más confusas, el carácter * también se utiliza al declarar variables de puntero. En esta declaración de la función:

void canonRect(struct rect *r) { 

r se declara ser un puntero a una struct rect. Esto es completamente diferente de usar * así:

canonRect(*r); 

En ambos casos, el carácter * significa algo completamente diferente.

+0

La persona que llama es responsable de liberar todas las memorias ubicadas dinámicamente. –

2

Es posible que desee leer sobre las diferentes maneras en que los parámetros pueden (conceptualmente) pasar a las funciones. C es call-by-value, por lo que cuando pasa el puntero a un rect en la función, está pasando una copia del puntero. Cualquier cambio que la función realice en el valor r directamente (no indirectamente) no será visible para la persona que llama.

Si desea que la función de proporcionar la persona que llama con una nueva estructura a continuación, hay dos maneras de hacerlo: 1. puede devolver un rect: 2. usted puede pasar un puntero a un puntero a un rect en :

la primera forma sería más natural:

struct rect* canonRect(struct rect* r) 
{ 
    struct rect* cr = (struct rect*) malloc(sizeof(struct rect)); 
    ... 
    return cr; 
} 

la segunda manera sería:

void canonRect(struct rect** r) 
{ 
    *r = (struct rect*) malloc(sizeof(struct rect)); 
} 

y la persona que llama entonces utilizar:

canonRect(&r); 

Pero la persona que llama pierde original del puntero que tenía, y que tendría que tener cuidado de no gotear estructuras.

Cualquiera que sea la técnica que utilice, la función necesitará asignar memoria para la nueva estructura en el montón con malloc. No puede asignar el espacio en la pila simplemente declarando una estructura porque esa memoria no es válida cuando la función retorna.

+0

Rob, creo que llegó a la base del problema, pero su respuesta podría ser mejor si incluyera un ejemplo de cómo ocurre la pérdida de la variable r original. Esta parece ser una pregunta introductoria, por lo que los codificadores C inexpertos podrían no captar los detalles. –

2

Primero, K & R c no tiene un concepto de "referencias", solo apuntadores. El operador & significa "tomar la dirección de".


segundo lugar, la r en cannonRect() es una variable local, y es no la r en main(). Cambiar donde los puntos locales r no afectan al r en la rutina de llamada.


Por último, como ya se ha señalado lo local struct rect se asigna en la pila, y se va fuera del alcance de la llave de cierre,

+0

Es cierto, pero el problema más grande era que necesitaba asignar a * r. –

-1

2. ¿Por qué podría yo tener una vía indirecta de compilación ilegal error de tiempo si trato de pasar un puntero a canonRect? (Es decir, si tuviera canonRect (* R); en main().)

Porque, no es el propósito de puntero.

Cuestiones relacionadas