Компилятор GCC — ошибка или неуказанное поведение?

Когда у меня есть конфликтующие определения ivars класса в цели-c (не переобъявление класса в том же файле, а скорее именования того же класса с разностью ivars, никакими предупреждениями, или еще лучше ошибки выпущены компилятором. Однако оба набора ivars применимы соответствующими методами в соответствующих файлах. Например,

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

компиляции без предупреждений или ошибок. Действительно ли это является намеренным? Действительно ли это - ошибка непроверенная? (для записи a и baz являются на самом деле той же ячейкой памяти.)

Править: для записи я говорю о iPhone OS, которой я верю, использует то же время выполнения в качестве MacOS на 64 бита

6
задан Jared Pochtar 12 May 2010 в 21:27
поделиться

2 ответа

Хотя код явно не работает, этот код должен компилироваться без предупреждения во всех случаях просто потому, что компилятор не выполняет у меня недостаточно информации, чтобы знать, как предупредить. При правильной компиляции он генерирует совершенно другую ошибку компоновщика только в 64-битной версии (что является следствием нового Objective-C ABI, а не напрямую из не хрупких ivars).

Если вы добавите int main () {} в foo.m, а затем скомпилируете его с помощью командной строки gcc -arch x86_64 foo.m -lobjc , ошибки ссылки будут прочь, потому что библиотека времени выполнения objc предоставляет пустые символы vtable, необходимые для завершения связи.

Во время компиляции воспринимайте каждый файл .m как изолированную единицу компиляции. Когда компилятор компилирует файл .m, он знает только то, что находится в этом файле .m, что обеспечивается чем-либо, импортированным в него.m, и - если для него настроен проект - то, что определено в предварительно скомпилированном заголовке проекта.

Таким образом, когда вы говорите в bar.m:

@interface foo {
float baz;
}

@end

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

Компилятор не имеет представления об объявлении в foo.m. Сгенерированный код описывает категорию класса foo, которая обращается к ivar baz. Если этот класс не существует во время компоновки, ошибка будет брошена в. Теперь, учитывая ваши foo.m и bar.m с моим добавлением основной функции, как указано выше, давайте попробуем несколько других компиляций:

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

Имеет смысл, потому что мы не определяли функцию main () в foo.m. 64-битная компиляция делает то же самое.

gcc -arch i386 bar.m -lobjc

Компилирует и связывает без предупреждения. Чтобы понять, почему, посмотрите на сгенерированные символы (удалено около дюжины нерелевантных):

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

Итак, двоичный файл содержит категорию с именем category в классе foo . Нет ошибки ссылки, потому что компоновщик на самом деле не пытается разрешить категории. Предполагается, что класс foo волшебным образом появится до того, как категория будет определена во время выполнения.

Вы можете следить за разрешением класса / категории среды выполнения с помощью 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

Итак, категория была помечена как ожидающая. Среда выполнения подключит его, как только появится foo !

Теперь, 64-разрядная версия ...

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

Ошибки связи возникают из-за того, что современный Objective-C ABI фактически заставляет генерировать правильные символы для переменных и категорий экземпляра по разным причинам, включая добавление метаданных, которые могут помочь в проверке программ. (как в этом случае).

Нет ошибок компиляции (что является правильным поведением), и ошибки ссылки имеют смысл.А как насчет того, чтобы связать их вместе?

В 32-битном случае все компилируется и связывается без ошибок. Таким образом, нам нужно взглянуть на символы и на отладку ObjC, чтобы увидеть, что происходит:

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)

Ага! Теперь есть класс foo , и среда выполнения подключает категорию к классу при запуске. Очевидно, что метод, возвращающий baz ivar, потерпит неудачу.

Однако 64-битный компоновщик не работает:

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

С добавлением символов для переменных экземпляра компоновщик теперь может обнаруживать ситуации, когда класс был повторно объявлен неправильно (как это было сделано в @interface ] бар.м).

18
ответ дан 8 December 2019 в 12:58
поделиться

Я думаю, что вы сделали расширенный класс.

0
ответ дан 8 December 2019 в 12:58
поделиться