Development Language Objective-C [Objective-C] プリミティブデータ型 - Primitive Data Types

概要

Objective-Cのプリミティブデータ型をまとめる。

注意

本記事はObjective-C 2.0を基準に作成された。

はじめに

Objective-Cを初めて勉強する際には、Foundationが提供するClassやType Aliasで再定義されたデータ型を厳密に区別して学習することが良いと考える。今回の記事ではPrimitive Data Typesについてまとめていく。

Objective-CはANSI Cのstrict supersetであるため、ANSI Cが提供するほとんどのデータ型だけでなく構造体(struct)や共用体(union)も使用できる。

まとめ

1. 文字データ型

1.1. char (32bit / 64bit)

TYPE サイズ 表現範囲
signed char 1byte Min : -128
Max : 127
unsigned char 1byte Min : 0
Max : 255

参考: charに格納されるデータ自体は整数だが、文字データ型として扱われる場合があるため整数データ型とは別に分類した。charは整数データ型であり文字データ型でもある。

2. 整数データ型

2.1. short int (32bit / 64bit)

TYPE サイズ 表現範囲
signed short int 2byte Min : -32,768
Max : 32,767
unsigned short int 2byte Min : 0
Max : 65,535

2.2. int (32bit / 64bit)

TYPE サイズ 表現範囲
signed int 4byte Min : -2,147,483,648
Max : 2,147,483,647
unsigned int 4byte Min : 0
Max : 4,294,967,295

2.3. long int (32bit)

TYPE サイズ 表現範囲
signed long int 4byte Min : -2,147,483,648
Max : 2,147,483,647
unsigned long int 4byte Min : 0
Max : 4,294,967,295

2.4. long int (64bit)

TYPE サイズ 表現範囲
signed long int 8byte Min : -9,223,372,036,854,775,808
Max : 9,223,372,036,854,775,807
unsigned long int 8byte Min : 0
Max : 18,446,744,073,709,551,615

2.5. long long int (32bit / 64bit)

TYPE サイズ 表現範囲
signed long long int 8byte Min : -9,223,372,036,854,775,808
Max : 9,223,372,036,854,775,807
unsigned long long int 8byte Min : 0
Max : 18,446,744,073,709,551,615

3. 浮動小数点データ型

3.1. float (32bit / 64bit)

TYPE サイズ 表現範囲
float 4byte Min : 1.175494e-38
Max : 3.402823e+38

3.2. double (32bit / 64bit)

TYPE サイズ 表現範囲
double 8byte Min : 2.225074e-308
Max : 1.797693e+308

4. 論理データ型

4.1. BOOL

論理値を表すために使われるBOOLは実はtypedefで再定義されたデータ型である。 コンパイラの種類やバージョン、またはバイナリが実行されるプラットフォーム環境によってintまたはcharが再定義された形態となる。 C言語ではC99以前にBOOLデータ型を開発者が以下のように自分で定義して使用していた。

typedef int bool

#define false 0
#define true  1

bool bValue;
bValue = true;

その後C99でブール値を格納するデータ型が正式に導入された。上記の例のように開発者たちはすでにC99以前からintデータ型をboolとして再定義して使用していたため、boolというキーワードではサポートできず、_BOOLというキーワードで論理値を表すデータ型をサポートした。

// stdbool.h

typedef _Bool bool;

#define true   (_Bool)1
#define false  (_Bool)0

しかし_BOOLboolに比べて少し長いため、上記で見られるようにstdbool.hで別途boolデータ型を定義して提供した。そのためC99で_BOOLの代わりにboolを使用したい場合はstdbool.hをincludeする必要がある。

ところで今見ているのはObjective-Cなのになぜ C言語の話を長々としているのか不思議に思うかもしれない。記事の冒頭で述べたようにObjective-CはC言語のstrict supersetであるため、Objective-CでもBOOLはコンパイラの種類やバージョン、プラットフォーム環境によって元のデータ型がintになることもcharになることもあった。

あったって? なることもあるではなく、なることもあったとはどういうことか?それはXcodeがClangを採用する前に使用していたGCC時代の話である。GCC時代にはバージョンによって元のデータ型がintになることもcharになることもあった。

以前はこのようなブール値を表すデータ型の違いによる問題が多く発生していたそうだ。現在はCコンパイラがANSI C基準をある程度よく遵守しているためブールデータ型として採用する元のデータ型がほぼ同一になり、true/false論理値も0と1にしっかり補正されるため問題が発生しない。

過渡期にはブールデータ型に0と1以外のデータ(元のデータ型が表現できるデータ)が入ることもあり、true/false比較の論理的欠陥による問題が多かったそうだ。さらにANSI C標準を本当に遵守しなかったVisual Studioの旧バージョンではboolcharBOOLintで開発者をさらに混乱させるケースもあったそうだ。こうした歴史を覚えておいて、古いバージョンのCコンパイラでブールデータ型を使う場合にはもう少し注意を払おう。

ではClangを採用した現在のXcodeではどうだろう?気になるので直接確認してみよう。

まずmacOS SDKとiOS SDKにあるobjc.hを見てみよう。

// objc.h

/// Type to represent a boolean value.

#if defined(__OBJC_BOOL_IS_BOOL)
    // Honor __OBJC_BOOL_IS_BOOL when available.
#   if __OBJC_BOOL_IS_BOOL
#       define OBJC_BOOL_IS_BOOL 1
#   else
#       define OBJC_BOOL_IS_BOOL 0
#   endif
#else
    // __OBJC_BOOL_IS_BOOL not set.
#   if TARGET_OS_OSX || TARGET_OS_MACCATALYST || ((TARGET_OS_IOS || 0) && !__LP64__ && !__ARM_ARCH_7K)
#      define OBJC_BOOL_IS_BOOL 0
#   else
#      define OBJC_BOOL_IS_BOOL 1
#   endif
#endif

#if OBJC_BOOL_IS_BOOL
    typedef bool BOOL;
#else
#   define OBJC_BOOL_IS_CHAR 1
    typedef signed char BOOL;
    // BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"
    // even if -funsigned-char is used.
#endif

おお!ヘッダからBOOL関連の内容だけ抜粋してきたのに複雑だ。 それでも一つずつ見ていこう。

4.1.1. 決定条件1

#if defined(__OBJC_BOOL_IS_BOOL)
    // Honor __OBJC_BOOL_IS_BOOL when available.
#   if __OBJC_BOOL_IS_BOOL
#       define OBJC_BOOL_IS_BOOL 1
#   else
#       define OBJC_BOOL_IS_BOOL 0
#   endif
#else

__OBJC_BOOL_IS_BOOLが定義されている場合はこの値に応じてOBJC_BOOL_IS_BOOL0または1で定義する。

4.1.2. 決定条件2

#else
    // __OBJC_BOOL_IS_BOOL not set.
#   if TARGET_OS_OSX || TARGET_OS_MACCATALYST || ((TARGET_OS_IOS || 0) && !__LP64__ && !__ARM_ARCH_7K)
#      define OBJC_BOOL_IS_BOOL 0
#   else
#      define OBJC_BOOL_IS_BOOL 1
#   endif
#endif

__OBJC_BOOL_IS_BOOLが定義されていない場合はターゲットOS、プラットフォーム、アーキテクチャの種類に応じてOBJC_BOOL_IS_BOOL0または1で定義する。

4.1.3. 決定条件3

#if OBJC_BOOL_IS_BOOL
    typedef bool BOOL;
#else
#   define OBJC_BOOL_IS_CHAR 1
    typedef signed char BOOL;
    // BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"
    // even if -funsigned-char is used.
#endif

決定条件1決定条件2によって決定されたOBJC_BOOL_IS_BOOLが真であればboolをBOOLとして再定義し、偽であればsigned charをBOOLとして再定義する。

4.1.4. 決定条件が正しいかテストしてみよう

おお!ヘッダを見てだいたいわかった。ではコードを書いてデバッグしながら確認してみよう。 まずmacOS Command Line Toolプロジェクトを作成し、以下のようにコードを書いてみる。

NSLog(@"__OBJC_BOOL_IS_BOOL = %@", __OBJC_BOOL_IS_BOOL ? @"defined" : @"undefined");
NSLog(@"TARGET_OS_OSX = %@", TARGET_OS_OSX ? @"YES" : @"NO");
NSLog(@"OBJC_BOOL_IS_BOOL = %@", OBJC_BOOL_IS_BOOL ? @"YES" : @"NO");
NSLog(@"OBJC_BOOL_IS_CHAR = %@", OBJC_BOOL_IS_CHAR ? @"YES" : @"NO");

結果は以下の通りだった。

__OBJC_BOOL_IS_BOOL = undefined
TARGET_OS_OSX = YES
OBJC_BOOL_IS_BOOL = NO
OBJC_BOOL_IS_CHAR = YES
Program ended with exit code: 0

先ほどまとめた3つの決定条件を見ながら出力結果を理解すると以下のように言えるだろう。

「Xcode 11.5、macOS 10.15 SDKでビルドしたmacOS Command Line Toolをx86_64、macOS Catalina 10.15.4環境で実行した場合、BOOLはcharを再定義したデータ型である。」

ではiOSデバイスでもテストしてみよう。iOS Single View Appプロジェクトを作成し、以下のようにコードを書いてみる。

NSLog(@"__OBJC_BOOL_IS_BOOL = %@", __OBJC_BOOL_IS_BOOL ? @"defined" : @"undefined");
NSLog(@"TARGET_OS_OSX = %@", TARGET_OS_OSX ? @"YES" : @"NO");
NSLog(@"OBJC_BOOL_IS_BOOL = %@", OBJC_BOOL_IS_BOOL ? @"YES" : @"NO");

結果は以下の通りだった。

__OBJC_BOOL_IS_BOOL = defined
TARGET_OS_OSX = NO
OBJC_BOOL_IS_BOOL = YES
Program ended with exit code: 0

おっ!これは以下のようにも言えるだろう。

「Xcode 11.5、iOS 13.5 SDKでビルドしたiOS Single View AppをiPhone 11 Pro、arm64、iOS 13.5.1環境で実行した場合、BOOLはintを再定義したデータ型である。 なぜならBOOLはboolを再定義したデータ型であり、boolはintを再定義したデータ型だからだ。」

ブー、違っていた。私が最後に使っていたC開発環境ではboolがintだったのでObjective-Cでも当然intだと思っていたがcharデータ型だった。 最初はsizeofで1が出た時は戸惑ったが、Apple開発者ドキュメントを調べるとこれに関する内容がしっかり記載されていた。

結局BOOLは以下の表で表現できるだろう。

TYPE Typedef
BOOL signed char YES NO

4.1.5. ヒント:プロセッサアーキテクチャの確認方法

Linuxでやっていたようにプロセッサアーキテクチャを確認しようと以下のコマンドを実行した。

arch
i386

結果はi386が出力された。いやいや、そんなはずがない。私のMacは64bitのはずなのにこれはどういうことだ? 驚いてGoogleで検索してみた。

なるほど、MacではarchはPowerPCかIntel CPUかによってIntel CPUの場合は無条件でi386を返すそうだ。つまりi386が出たら2-way universal32-bit onlyのどちらかということだ。

ではmachine hardware nameを確認してみよう。以下のコマンドを実行すればよい。

machine
x86_64h

または

uname -m
x86_64

そうだよね。そんなはずがないよね。安心〜

5. インスタンスポインタ型

5.1. id (32bit)

TYPE サイズ 表現範囲
id 4byte インスタンスポインタ

5.2. id (64bit)

TYPE サイズ 表現範囲
id 8byte インスタンスポインタ
// objc.h

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

idが表現できるデータはインスタンスポインタと言ったがもう少し厳密に言うとisaポインタをインスタンス変数として持っている構造体objc_objectのポインタを格納できる。 C言語のvoidポインタと同じ役割を果たし、idポインタを利用してポリモーフィズム、つまり動的バインディングと動的型付けを実装できる。isaはObjective-Cランタイムを扱う際に詳しくまとめる。

6. フォーマット指定子型

データ型 フォーマット文字
char %c
signed short int %hd %hi %hx %ho
unsigned short int %hu %hx %ho
signed int %d %i %x %o
unsigned int %u %x %o
signed long int %ld %li %lx %lo
unsigned long int %lu %lx %lo
signed long long int %lld %lli %llx %llo
unsigned long long int %llu %llx %llo
float %f %e %g %a
double %f %e %g %a
id %p

Xcode 12でテストしてみたがsignedとunsigned間でフォーマット文字を混用してもビルド警告はなかった。それでも下位互換性と問題対策のためにはこれらを区別して正確に使用するのが良いだろう。

自分の目で直接確認してみよう

では各データ型のサイズと最小値、最大値を直接確認してみよう。 私は32bitデバイスを持っていないため64bitデバイスでのみ確認する。

1. まず各データ型のサイズを見てみよう

1.1. 文字データ型

1.1.1. char (64bit)

NSLog(@"size of char is %ld", sizeof(char));
NSLog(@"size of unsigned char is %ld", sizeof(unsigned char));
size of char is 1
size of unsigned char is 1
Program ended with exit code: 0

1.2. 整数データ型

1.2.1. short int (64bit)

NSLog(@"size of short int is %ld", sizeof(short int));
NSLog(@"size of unsigned short int is %ld", sizeof(unsigned short int));
size of short int is 2
size of unsigned short int is 2
Program ended with exit code: 0

1.2.2. int (64bit)

NSLog(@"size of int is %ld", sizeof(int));
NSLog(@"size of unsigned int is %ld", sizeof(unsigned int));
size of int is 4
size of unsigned int is 4
Program ended with exit code: 0

1.2.3. long int (64bit)

NSLog(@"size of long int is %ld", sizeof(long int));
NSLog(@"size of unsigned long int is %ld", sizeof(unsigned long int));
size of long int is 8
size of unsigned long int is 8
Program ended with exit code: 0

1.2.4. long long int (64bit)

NSLog(@"size of long long int is %ld", sizeof(long long int));
NSLog(@"size of unsigned long long int is %ld", sizeof(unsigned long long int));
size of long long int is 8
size of unsigned long long int is 8
Program ended with exit code: 0

1.3. 浮動小数点データ型

1.3.1. float (64bit)

NSLog(@"size of float is %ld", sizeof(float));
size of float is 4
Program ended with exit code: 0

1.3.2. double (64bit)

NSLog(@"size of double is %ld", sizeof(double));
size of double is 8
Program ended with exit code: 0

1.4. 論理データ型

1.4.1. BOOL (64bit)

NSLog(@"size of BOOL is %ld", sizeof(BOOL));
size of BOOL is 1
Program ended with exit code: 0

1.5. インスタンスポインタ型

1.5.1. id (64bit)

NSLog(@"size of id is %ld", sizeof(id));
size of id is 8
Program ended with exit code: 0

2. 次に各データ型の最小値と最大値を出力してみよう

2.1. 文字データ型

2.1.1. char (64bit)

NSLog(@"CHAR MIN is %d, CHAR MAX is %d", CHAR_MIN, CHAR_MAX);
NSLog(@"UCHAR MAX is %u", UCHAR_MAX);
CHAR MIN is -128, CHAR MAX is 127
UCHAR MAX is 255
Program ended with exit code: 0

2.2. 整数データ型

2.2.1. short int (64bit)

NSLog(@"SHRT MIN is %hd, SHRT MAX is %hd", SHRT_MIN, SHRT_MAX);
NSLog(@"USHRT MAX is %hu", USHRT_MAX);
SHRT MIN is -32768, SHRT MAX is 32767
USHRT MAX is 65535
Program ended with exit code: 0

2.2.2. int (64bit)

NSLog(@"INT MIN is %d, INT MAX is %d", INT_MIN, INT_MAX);
NSLog(@"UINT MAX is %u", UINT_MAX);
INT MIN is -2147483648, INT MAX is 2147483647
UINT MAX is 4294967295
Program ended with exit code: 0

2.2.3. long int (64bit)

NSLog(@"LONG MIN is %ld, LONG MAX is %ld", LONG_MIN, LONG_MAX);
NSLog(@"ULONG MAX is %lu", ULONG_MAX);
LONG MIN is -9223372036854775808, LONG MAX is 9223372036854775807
ULONG MAX is 18446744073709551615
Program ended with exit code: 0

2.2.4. long long int (64bit)

NSLog(@"LLONG MIN is %lld, LLONG MAX is %lld", LLONG_MIN, LLONG_MAX);
NSLog(@"ULLONG MAX is %llu", ULLONG_MAX);
LLONG MIN is -9223372036854775808, LLONG MAX is 9223372036854775807
ULLONG MAX is 18446744073709551615
Program ended with exit code: 0

2.3. 浮動小数点データ型

2.3.1. float (64bit)

NSLog(@"FLT MIN is %f, FLT MAX is %f", FLT_MIN, FLT_MAX);
FLT MIN is 0.000000, FLT MAX is 340282346638528859811704183484516925440.000000
Program ended with exit code: 0

2.3.2. double (64bit)

NSLog(@"DBL MIN is %f, DBL MAX is %f", DBL_MIN, DBL_MAX);
DBL MIN is 0.000000, DBL MAX is 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000
Program ended with exit code: 0

2.4. 論理データ型

2.4.1. BOOL (64bit)

BOOL yes = YES;
BOOL no = NO;

NSLog(@"YES is %d", yes);
NSLog(@"NO is %d", no);
YES is 1
NO is 0
Program ended with exit code: 0

2.5. インスタンスポインタ型

2.5.1. id (64bit)

id object = [[NSObject alloc] init];
NSLog(@"object address is %p", &object);
object address is 0x7ffeeb41b078
Program ended with exit code: 0

このメモリアドレスは実行のたびに変更される。

ここまでObjective-Cで提供するプリミティブデータ型を見てきた。私のように勉強する時に底まで見ないと気が済まない性格の人は「NSIntegerとintの違いは何?」「CGFloatとfloatの違いは何?」 という疑問がさらに生まれるかもしれない。これは直接ヘッダを開いて確認してみよう。ヘッダは仕様そのものである。

実際の開発ではアーキテクチャ環境やプラットフォームに応じて適切なデータ型をtypedefで再定義したデータ型を主に使用すると思う。 しかしプリミティブデータ型をよく理解していることがいつか役に立つ時が来ると考えている。

[+] 32bitデバイスをお持ちの方は上記のコードを実行して結果をコメントに残していただければ本文を更新する。

参考

コメントする