2010-03-20 30 views
6

He estado usando el siguiente código para crear varias struct, pero solo le doy un puntero a las personas que están fuera del archivo C. (Sí, sé que podrían potencialmente perder el tiempo con eso, así que no es del todo como la palabra clave privada en Java, pero me parece bien).Typedef y Struct en archivos C y H

De todos modos, he estado usando el siguiente código, y lo he visto hoy, y estoy realmente sorprendido de que realmente funcione, ¿alguien puede explicar por qué?

En mi archivo C, creo mi estructura, pero no le doy una etiqueta en el espacio de nombres typedef:

struct LABall { 
    int x; 
    int y; 
    int radius; 
    Vector velocity; 
}; 

Y en el archivo H, puse esto:

typedef struct LABall* LABall; 

Obviamente, estoy usando #include "LABall.h" en el archivo c, pero NO estoy usando #include "LABall.c" en el archivo de encabezado, ya que eso anularía el propósito de un archivo de encabezado separado. Entonces, ¿por qué puedo crear un puntero a la estructura LABall * en el archivo H cuando no lo he incluido realmente? ¿Tiene algo que ver con el espacio de nombres struct que funciona entre archivos, incluso cuando un archivo no está vinculado de ninguna manera con otro?

Gracias.

+2

Ocultar el hecho de que algo es un puntero generalmente se considera un mal estilo. Y no es necesario: los punteros opacos funcionan bien en C, eche un vistazo a FILE *, por ejemplo, no hay ningún tipo de puntero de ARCHIVO especial. –

+0

Bien, gracias. Estoy demasiado acostumbrado a lame java, que parece encantado de ocultar el hecho de que todo es un puntero. –

Respuesta

7

Ya que está preguntando una razón precisa sobre "por qué" el lenguaje funciona de esta manera, supongo que quiere algunas referencias precisas. Si usted encuentra que pedante, omita las notas ...

Funciona debido a dos cosas:

  • Todo puntero a la estructura tipos tienen la misma representación (tenga en cuenta que no es cierto para todos tipos de puntero, en lo que se refiere al estándar C). [1] Por lo tanto, el compilador tiene suficiente información para generar el código adecuado para todos los usos de su tipo de puntero a estructura.

  • La etiqueta namespace (struct, enum, union) es de hecho compatible en todas las unidades de traducción. [2] Por lo tanto, las dos estructuras (a pesar de que una no está completamente definida, es decir, carece de declaraciones de miembros) son una y la misma.

(BTW, import no es estándar.)

[1] Según n1256 §6.2.5.27:

todos los punteros de estructurar tipos tendrán la misma representación y requisitos de alineación entre sí. Los punteros a otros tipos no necesitan tener los mismos requisitos de representación o alineación.

[2] De acuerdo con n1256 §6.2.7.1:

estructura de dos, unión, o de los tipos enumerados declarados en unidades de traducción separadas son compatibles si sus etiquetas y miembros cumplan las condiciones siguientes: Si uno se declara con una etiqueta, la otra se declarará con la misma etiqueta. Si ambos son tipos completos, se aplican los siguientes requisitos adicionales: [no nos concierne].

+1

"(Por cierto, # importación no es estándar.)" Woops, estaba mezclando Java ... Quise escribir #include. :) Arreglaré eso. –

1

En

typedef struct A* B; 

desde las interfaces de todos los punteros son los mismos, sabiendo que B significa un puntero a una estructura A contiene información suficiente ya. La implementación real de A es irrelevante (esta técnica se llama "puntero opaco".)

(Por cierto, mejor cambiar el nombre de una de las LABall 's. Es confuso que el mismo nombre se utiliza para tipos incompatibles.)

+0

Realmente, pensé que el hecho de que solo uno de ellos existiera en el espacio de nombres struct, y uno de ellos existiera en el espacio de nombres typedef sería suficiente para distinguirlos? Oh, bueno, lo haré, gracias. –

+3

@Leif: Está bien para * compilador *, pero los * usuarios * estarán confundidos, especialmente aquellos que usan C++. – kennytm

22

un patrón estándar para ese tipo de cosas es tener un archivo foo.h definir la API como

typedef struct _Foo Foo; 

Foo *foo_new(); 
void foo_do_something(Foo *foo); 

y un archivo foo.c proporcionar una implementación para que la API como

struct _Foo { 
    int bar; 
}; 

Foo *foo_new() { 
    Foo *foo = malloc(sizeof(Foo)); 
    foo->bar = 0; 
    return foo; 
} 

void foo_do_something(Foo *foo) { 
    foo->bar++; 
} 

Esto oculta todo el diseño de memoria y el tamaño de la estructura en la implementación de foo.c, y la interfaz expuesta a través de foo.h es completamente independiente de las partes internas: Un caller.c cuales sólo #include "foo.h" sólo tendrá que almacenar un puntero a algo, y punteros son siempre del mismo tamaño:

#include "foo.h" 

void bleh() { 
    Foo *f = foo_new(); 
    foo_do_something(f); 
} 

Nota: me queda liberar la memoria como un ejercicio para el lector. :-)

Por supuesto, esto significa que el siguiente archivo de broken.c se NO trabajo:

#include "foo.h" 

void broken() { 
    Foo f; 
    foo_do_something(&f); 
} 

como el tamaño de la memoria necesaria para crear realmente una variable de tipo Foo no se conoce en este archivo.