Mặc dù rõ ràng bị hỏng, mã đó nên biên dịch mà không cần cảnh báo trong mọi trường hợp đơn giản vì trình biên dịch không có đủ thông tin để biết cách cảnh báo. Khi được biên dịch một cách chính xác, nó tạo ra một lỗi liên kết hoàn toàn khác nhau chỉ trong 64-bit (đó là bụi phóng xạ từ Mục tiêu-C ABI mới, chứ không phải trực tiếp từ các ivars không mong manh).
Nếu bạn thêm int main() {}
vào foo.m và sau đó biên dịch nó bằng dòng lệnh gcc -arch x86_64 foo.m -lobjc
, lỗi liên kết sẽ biến mất vì thư viện runtime objc cung cấp biểu tượng vtable trống cần thiết để hoàn thành liên kết.
Trong quá trình biên soạn, hãy suy nghĩ từng tệp .m dưới dạng đơn vị biên dịch riêng biệt. Khi trình biên dịch biên dịch một tệp .m, nó chỉ có kiến thức về những gì nằm trong tệp .m đó, những gì được cung cấp bởi bất kỳ thứ gì được nhập vào tệp .m đó và - nếu một dự án được cấu hình cho nó - những gì được định nghĩa trong tiêu đề được biên dịch trước của dự án.
Vì vậy, khi bạn nói trong bar.m:
@interface foo {
float baz;
}
@end
@implementation foo (category)
- (float)blah {
return baz;
}
@end
int main() {}
Trình biên dịch không có khái niệm về việc kê khai trong foo.m. Mã được tạo ra mô tả một danh mục trên lớp foo truy cập vào baz ivar. Nếu lớp đó không tồn tại ở thời gian liên kết, lỗi sẽ được gửi trong Hiện hành, với foo.m và bar.m của bạn khi tôi thêm một chức năng chính như trên, hãy thử một số cách biên dịch khác:
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
Làm cho tinh thần bởi vì chúng tôi đã không xác định một hàm main() trong foo.m. 64 bit biên dịch không giống nhau.
gcc -arch i386 bar.m -lobjc
Biên dịch và liên kết mà không cảnh báo. Để hiểu tại sao, nhìn vào những biểu tượng được tạo ra (xóa khoảng một chục người không liên quan):
nm -a a.out
00001f52 t -[foo(category) blah]
00000000 A .objc_category_name_foo_category
Vì vậy, nhị phân chứa một loại tên category
trên lớp foo
. Không có lỗi liên kết vì trình liên kết không thực sự cố gắng giải quyết các danh mục. Nó giả định rằng lớp foo
sẽ xuất hiện một cách kỳ diệu trước khi thể loại được giải quyết trong thời gian chạy.
Bạn có thể làm theo cùng với độ phân giải lớp/category của bộ thực thi với một 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
Vì vậy, hạng mục đã được đánh dấu là chưa giải quyết. Thời gian chạy sẽ treo nó ngay sau khi foo
đi vào sự tồn tại!
Bây giờ, 64 bit ...
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
Các lỗi liên kết là vì hiện đại Objective-C ABI thực sự gây ra những biểu tượng thích hợp để được phát ra cho các biến dụ và loại cho một loạt các lý do, bao gồm thêm siêu dữ liệu có thể giúp xác nhận các chương trình (như trong trường hợp này).
Không có lỗi biên dịch nào (hành vi đúng) và lỗi liên kết có ý nghĩa. Bây giờ, làm thế nào về liên kết hai với nhau?
Trong trường hợp 32 bit, mọi thứ biên dịch và liên kết mà không có lỗi. Vì vậy, chúng tôi sẽ cần phải nhìn vào các biểu tượng và tại spug gỡ lỗi ObjC để xem những gì đang xảy ra:
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! Bây giờ có một lớp foo
và thời gian chạy kết nối danh mục với lớp khi khởi động. Rõ ràng, phương pháp trả lại là ngà baz
sẽ thất bại một cách ngoạn mục.
Phiên bản 64 bit mối liên kết thất bại, mặc dù:
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
Với việc bổ sung các biểu tượng cho các biến Ví dụ, các mối liên kết bây giờ có thể bắt các tình huống mà một lớp đã được redeclared không đúng cách (như đã được thực hiện trong @interface
của men).
vì vậy theo câu hỏi ban đầu của tôi: đây có phải là hành vi có chủ ý hoặc lỗi không được kiểm tra trên một phần của trình biên dịch không? Ngoài ra, đối với iPad, điều này đang được thử nghiệm trong thời gian chạy 'hiện đại' nhưng trên trình mô phỏng, có nghĩa là thực tế là i386, vì vậy nó tuân theo quy tắc 64 bit hoặc quy tắc 32 bit như được định nghĩa ở trên (khi bạn sử dụng các cụm từ này thay vì mới) thời gian chạy cũ) và quan trọng hơn, điều này có thay đổi trên thiết bị không? Và cuối cùng, khi quay trở lại baz ivar, không có sự thất bại ngoạn mục nào xảy ra vì nó là cùng một vị trí bộ nhớ như a, bằng chứng là * (int *) & baz cho giá trị của –
Có thể và không biên dịch mà không cảnh báo. Nó sẽ gây ra lỗi liên kết trên trình biên dịch 64 bit trên máy tính để bàn và khi nhắm mục tiêu thiết bị, nhưng sẽ liên kết tốt (nhưng chạy sai) trong trình mô phỏng. Nếu bạn đang mong đợi để có thể lưu trữ một giá trị trong cả hai 'baz' và' a', nó sẽ thất bại một cách ngoạn mục, dưới thời gian chạy của trình giả lập, hai hiệu quả chia sẻ không gian lưu trữ. – bbum