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).
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 –
. 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