2010-05-12 14 views
6

Cuando tengo definiciones contradictorias de los ivars de una clase en Object-C (no redeclarando la clase en el mismo archivo, sino nombrando la misma clase con diferencias, el compilador no emite advertencias o, mejor aún, comete errores). , ambos conjuntos de ivars son utilizables por los métodos apropiados en los archivos respectivos por ejemploCompilador de GCC: ¿error o comportamiento no especificado?

Foo.m:.

@interface foo { 
int a; 
} 
- (int)method; 
@end 

@implementation foo 

- (int)method { 
    return a; 
} 

@end 

Bar.m:

@interface foo { 
float baz; 
} 

@end 

@implementation foo (category) 
- (float)blah { 
    return baz; 
} 
@end 

compila sin advertencias o errores. Es esto intencional? ¿Es esto un error no verificado? (Para el registro, y una Baz son en realidad la misma posición de memoria.)

Editar: para que conste que estoy hablando iPhone OS, que creo que utiliza el mismo tiempo de ejecución como MacOS 64 bits

Respuesta

18

Aunque obviamente está roto, ese código debería compilar sin previo aviso en todos los casos, simplemente porque el compilador no tiene suficiente información para saber cómo advertir. Cuando se compila correctamente, genera un error de enlazador completamente diferente solo en 64 bits (que es consecuencia del nuevo Objective-C ABI, no directamente de los ivars no frágiles).

Si se agrega a int main() {} foo.m y luego compilarlo con la línea de comandos gcc -arch x86_64 foo.m -lobjc, los errores de enlace desaparece porque la biblioteca de tiempo de ejecución objc proporciona los símbolos vtable vacíos necesarios para completar el enlace.

Durante la compilación, piense en cada archivo .m como una unidad de compilación aislada. Cuando el compilador compila un archivo .m, solo tiene conocimiento de lo que está en ese archivo .m, lo que proporciona cualquier cosa importada en ese archivo .m y, si un proyecto está configurado para él, lo que se define en el encabezado precompilado del proyecto.

Por lo tanto, cuando usted dice en bar.m:

@interface foo { 
float baz; 
} 

@end 

@implementation foo (category) 
- (float)blah { 
    return baz; 
} 
@end 
int main() {} 

El compilador no tiene noción de la declaración en foo.m. El código generado describe una categoría en la clase foo que accede a ivar baz. Si esa clase no existe en tiempo de enlace, un error será lanzada en Ahora bien, dado que su foo.m y bar.m con mi adición de una función principal que el anterior, vamos a tratar algunas compilaciones diferentes:

gcc -arch i386 foo.m -lobjc 
Undefined symbols: 
    "_main", referenced from: 
     start in crt1.10.6.o 
ld: symbol(s) not found 
collect2: ld returned 1 exit status 

Tiene sentido porque no definimos una función main() en foo.m. La compilación de 64 bits hace lo mismo.

gcc -arch i386 bar.m -lobjc 

Compila y enlaces sin previo aviso. Para entender por qué, un vistazo a los símbolos generados (suprimido una docena de los irrelevantes):

nm -a a.out 
00001f52 t -[foo(category) blah] 
00000000 A .objc_category_name_foo_category 

Así, el binario contiene una categoría denominada category en clase foo. No hay error de enlace porque el vinculador no intenta resolver las categorías. Asume que la clase foo aparecerá mágicamente antes de que la categoría se resuelva en tiempo de ejecución.

Puede seguir junto con la resolución de clase/categoría del tiempo de ejecución con una Ivar:

env OBJC_PRINT_CLASS_SETUP=YES ./a.out 
objc[498]: CONNECT: pending category 'foo (category)' 
objc[498]: CONNECT: class 'Object' now connected (root class) 
objc[498]: CONNECT: class 'Protocol' now connected 
objc[498]: CONNECT: class 'List' now connected 

Así, la categoría fue marcado como pendiente. ¡El tiempo de ejecución lo conectará tan pronto como foo entre en existencia!

Ahora, 64 bits ...

gcc -arch x86_64 bar.m -lobjc 
Undefined symbols: 
    "_OBJC_IVAR_$_foo.baz", referenced from: 
     -[foo(category) blah] in ccvX4uIk.o 
    "_OBJC_CLASS_$_foo", referenced from: 
     l_OBJC_$_CATEGORY_foo_$_category in ccvX4uIk.o 
     objc-class-ref-to-foo in ccvX4uIk.o 
ld: symbol(s) not found 

Los errores de enlace se deben a que la moderna Objective-C ABI realmente causa símbolos adecuados para ser emitidos por las variables de instancia y categorías para una variedad de razones, incluyendo la adición de metadatos que puede ayudar a validar los programas (como lo hizo en este caso).

No hay errores de compilación (que es el comportamiento correcto) y los errores de enlace tienen sentido. Ahora, ¿qué hay de vincular los dos juntos?

En el caso de 32 bits, todo se compila y enlaces sin error. Por lo tanto, tendremos que mirar los símbolos y en la depuración ObjC vomitar para ver lo que está pasando:

gcc -arch i386 bar.m foo.m -lobjc 
nm -a a.out 
00001e0f t -[foo method] 
00001dea t -[foo(category) blah] 
00000000 A .objc_category_name_foo_category 
00003070 S .objc_class_name_foo 
env OBJC_PRINT_CLASS_SETUP=YES ./a.out 
objc[530]: CONNECT: attaching category 'foo (category)' 
objc[530]: CONNECT: class 'Object' now connected (root class) 
objc[530]: CONNECT: class 'Protocol' now connected 
objc[530]: CONNECT: class 'List' now connected 
objc[530]: CONNECT: class 'foo' now connected (root class) 

Aha! Ahora hay una clase foo y el tiempo de ejecución conecta la categoría a la clase al inicio. Obviamente, el método que devuelve el baz ivar va a fallar espectacularmente.

El enlazador 64 bit falla, sin embargo:

gcc -arch x86_64 bar.m foo.m -lobjc 
Undefined symbols: 
    "_OBJC_IVAR_$_foo.baz", referenced from: 
     -[foo(category) blah] in ccBHNqzm.o 
ld: symbol(s) not found 
collect2: ld returned 1 exit status 

Con la adición de los símbolos para las variables de instancia, el enlazador puede ahora ponerse situaciones donde una clase se ha redeclarada incorrectamente (como se hizo en el @interface de levadura de cerveza).

+0

según mi pregunta original: ¿se trata de un comportamiento intencional o un error no comprobado por parte del compilador? Además, para el iPad, esto se está probando en el tiempo de ejecución 'moderno' pero en el simulador, lo que significa que es realmente i386, así que seguiría reglas de 64 bits o reglas de 32 bits como se definió anteriormente (ya que usaste estos términos en vez de nuevo/viejo tiempo de ejecución), y más importante, ¿esto cambiaría en el dispositivo? Y por último, en lo que respecta a devolver el baz ivar, no se produce una falla espectacular, ya que es la misma ubicación de memoria que a, como se evidencia por * (int *) & baz que arroja el valor de –

+0

. El podría compilar sin previo aviso. Causará un error de enlace en la compilación de escritorio de 64 bits y al dirigirse al dispositivo, pero se vinculará bien (pero se ejecutará erróneamente) en el simulador. Si espera poder almacenar un valor en 'baz' y' a', fallará espectacularmente ya que, en el tiempo de ejecución del simulador, los dos comparten el espacio de almacenamiento. – bbum

Cuestiones relacionadas