雖然明顯被破壞,但是代碼應該在所有情況下編譯時都沒有警告,只是因爲編譯器沒有足夠的信息來知道如何警告。編譯正確時,它僅在64位(這是來自新的Objective-C ABI,而不是直接來自非易碎的ivars)中產生完全不同的鏈接器錯誤。
如果您將int main() {}
添加到foo.m中,然後使用命令行gcc -arch x86_64 foo.m -lobjc
進行編譯,則鏈接錯誤消失,因爲objc運行時庫提供了完成鏈接所需的空白vtable符號。
在編譯期間,將每個.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
有意義,因爲我們沒有在foo.m中定義main()函數。 64位編譯也是一樣的。
gcc -arch i386 bar.m -lobjc
編譯並沒有警告鏈接。要理解爲什麼,看所產生的符號(刪除大約有十幾個不相關的):
nm -a a.out
00001f52 t -[foo(category) blah]
00000000 A .objc_category_name_foo_category
所以,二進制包含在foo
類名爲category
類別。沒有鏈接錯誤,因爲鏈接器實際上沒有嘗試解析類別。它假定類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
的鏈接錯誤是因爲現代的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
,運行時啓動時將類連接到類。顯然,返回伊娃的方法將失敗壯觀。
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)。
所以根據我原來的問題:這是一個有意的行爲或編譯器的一部分未經檢查的錯誤?另外,對於iPad來說,這是在'現代'運行時測試,但在模擬器上進行測試,這意味着它實際上是i386,所以它會遵循上面定義的64位規則或32位規則(因爲您使用這些術語而不是新/舊的運行時),更重要的是,這將改變設備?最後,只要返回巴伐利亞伊阿爾去,沒有發生壯觀的失敗,因爲它是一個相同的內存位置,由*(int *)&baz產生的值的 – 2010-05-13 00:32:48
該應該和沒有警告編譯。它會導致64位桌面編譯時的鏈接錯誤以及定位設備時出現鏈接錯誤,但會在模擬器中正常鏈接(但運行錯誤)。如果你希望能夠在'baz'和'a'中存儲一個值,它將會失敗,因爲在模擬器的運行時間下,這兩者有效地共享存儲空間。 – bbum 2010-05-13 03:17:17