Компилятор GCC - ошибка или неопределенное поведение? - PullRequest
6 голосов
/ 12 мая 2010

Когда у меня есть противоречивые определения ivars класса в target-c (не перераспределяя класс в том же файле, а скорее присваивая имя тому же классу с помощью diff 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, который, как я считаю, использует ту же среду выполнения, что и 64-битный MacOS

Ответы [ 2 ]

18 голосов
/ 12 мая 2010

Хотя этот код явно не работает, этот код должен компилироваться без предупреждения во всех случаях просто потому, что у компилятора недостаточно информации, чтобы знать, как предупреждать. При правильной компиляции он генерирует совершенно другую ошибку компоновщика только в 64-битной версии (что не соответствует новому ABI Objective-C, а не напрямую из не хрупких 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. Если этот класс не существует во время компоновки, в Now появится ошибка, учитывая ваши 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 волшебным образом появится до того, как категория будет разрешена во время выполнения.

Вы можете следовать вместе с разрешением класса / категории среды выполнения с помощью ивара:

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

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

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

В 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)

Aha! Теперь есть класс 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 в bar.m).

0 голосов
/ 12 мая 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...