星期日, 五月 24, 2009

用 Objective-C 從二進位檔案(binary file)中讀入數值的 byte order 問題

要從二進位檔案中讀入數值,其中一個會遇到的問題就是 Little-endian 和 Big-endian,這和 CPU 的種類有關。Intel 系列的 x86 CPU 使用的是 Little-endian,而 PowerPC、ARM 等的 CPU 則是採用 Big-endian。endian 不同的問題,在以前 PPC 的時代算是相當重要,後來改用 Intel CPU 之後重要性就下降了許多,不過現在因為 iPhone 採用的 ARM 又是 Big-endian,所以這問題的重要性又大幅提升了,尤其許多新手設計師,之前可能根本沒碰過非 Intel 的平台,根本沒考慮過這問題,於是寫出來的程式就會讀到一堆詭異的數字。

首先我們要先界定 endian 的問題會影響到哪些資料型別。所謂 endian 不同,是因為高低位元在記憶體中表示的方式不同而造成的,Little-endian 將高位元放在記憶體位址較大的地方,Big-endian 則反之。舉例來說,0x00112233 這個 16 進位的數字,在 Little-endian 的機器上,在記憶體中呈現的是 33 22 11 00,而在 Big-endian 的機器上則呈現 00 11 22 33。因此,假如把在 Little-endian 機器上寫入的二進位檔拿給 Big-endian 的機器讀取,讀出來的數字就會出錯。

從這邊我們可以發現,要發生 endian 的問題,首先資料型別必須用超過 2 個 byte 來儲存才有可能,因此只用一個 byte 儲存的型別,例如 char,就沒有 endian 的問題。

至於 int、long 之類的整數型別,可以用 CFSwap[type][Big/Littile/Host]To[Big/Littile/Host] 系列函式來轉換。其中的 type 可能是 Int16、Int32 等等。使用方法如下:
int littleEndian = 1234;
int hostEndian = CFSwapInt16LittleToHost(littleEndian);
那麼 hostEndian 的變數值就是採用目前機器上的 endian 順序的 int。

不過浮點數就沒這麼輕鬆了。我們在前面的例子中還是用 int 來存放 littleEndian,因為就算 byte order 不對了,但它仍然是個 valid 的 integer,不過浮點數的每個 bit 有它不同的意義,因此一旦 byte order 解譯錯誤,它就可能變成 invalid float,導致程式直接當掉。

為了解決這個問題,Objective-C 中用了一個與 byte order 無關的資料結構來儲存這個資料,定義如下:
struct CFSwappedFloat32 {
uint32_t v;
};
typedef struct CFSwappedFloat32 CFSwappedFloat32;
可以看到,這個結構中就只有一個 data,uint32_t,Objective-C 就是用它來儲存這個 float 的資料(當然,此時系統並不會把它當成一個 float)。接下來的處理方式就是,將這個 uint32_t 裡面的 byte 頭尾互換位置,然後將這 32 個 bit(4 個 byte)轉化成 float。而這就是 CFConvertFloat32SwappedToHost 這個函式在作的事。整個的用法可以用以下的例子表示:
CFSwappedFloat32 littleFloat;
long buffer;
float hostFloat;

fread(&buffer, sizeof(long), 1, fp);
littleFloat.v = buffer;
hostFloat = CFConvertFloat32SwappedToHost(littleFloat);
當然,其中的 fread 可以用 memcpy、NSData 的 byte method 等等取代,只要把握住一個原則:「 CFSwappedFloat32 的內部是用 uint32_t 來儲存這個待轉換的 float」即可。

標籤: , , ,

NSNumber 與 NSInteger

在 Mac OSX 10.5 中新增加了 NSInteger 這個東西,但是這下就會跟 NSNumber 搞混,到底要用哪個?

基本上,NSInteger(以及類似的 NSUInteger)是為了解決在 32-bit/64-bit CPU 架構下,int 長度不同的問題,它的定義如下:

#if __LP64__ || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
也就是說,NSInteger 只是用 typedef 包裝過的 int/long,它是個基本資料型別(data type)。

NSNumber 則是個不折不扣的 Objective-C 類別,因此這兩者的使用時機就很明顯了,當你要把 integer 放到 Obj-C 的集合,例如 NSArray/NSSet 時,請用 NSNumber,不然其他的時候用 NSInteger 就好了。

標籤: , , ,

星期三, 十一月 07, 2007

Xcode 2.5 & 3.0 發表

Apple 前幾天發表了 Xcode 2.5 及 3.0,其中 Xcode 2.5 可以安裝在 10.4 Tiger 和 10.5 Leopard 上,而 Xcode 3.0 則只能安裝在 10.5 Leopard。

根據 Release Note,Xcode 2.5 除了修改了 2.4.1 的問題之外,最主要的功能就是在 Leopard 上為那些還沒有準備好要轉移到 Xcode 3 的專案提供一個比較平緩的轉移方式。

Xcode 3.0 則是一個大改版,包含了 Objective-C 2.0 的支援、Dashcode、Interface Builder 3 等等。

不過要注意的是,Objective-C 2.0 只能在 Leopard 上執行,因此必須搭配 10.5 SDK。假如要在 Leopard 上寫能在 Tiger 執行的程式,就必須嚴守 Objective-C 1.0 和 10.4 SDK 才行。

Xcode 可以在 Apple 的開發者網站下載。
Objective-C 2.0 的官方文件在 The Objective-C 2.0 Programming Language

標籤: , ,