Edited at

NSInteger→longのキャストについて考える

More than 3 years have passed since last update.


はじめに

私は、hogeというNSIntegerの変数の値をログ出力する時に、

NSInteger hoge = 100;

NSLog(@"output hoge = %ld", (long)hoge);

といったコードを書いていました。

NSStringでNSIntegerの変数の値を扱う場合は、

NSInteger hoge = 100;

NSString *hogeString = [NSString stringWithFormat:@"%ld", (long)hoge];

みたいな感じですね。

このlongにキャストしているコードについて、

NSNumberを使う or 書式指定子を%zdにしましょうと先日教わりました。

(本記事では、NSNumberについては特に触れません。)

そもそもなぜNSIntegerはlongにキャストしなければいけなかったのかなーということを含めて整理します。


NSIntegerの値をログ出力してみる


1.NSIntegerの値を指定子%dでログ出力

NSInteger hoge = 100;

NSLog(@"output hoge = %d", hoge);

caseA. iPhone5以前の端末(and iPhone5c)

→怒られない。

スクリーンショット 2016-08-20 12.00.43.png

caseB. iPhone5s以降の端末

→怒られる。

スクリーンショット 2016-08-20 11.27.30.png

指定子%dを使用した場合は、

32bit端末では怒られないが、64bit端末では怒られる

ということですね。


2.NSIntegerの値を指定子%ldでログ出力

NSInteger hoge = 100;

NSLog(@"output hoge = %ld", hoge);

caseA. iPhone5以前の端末(and iPhone5c)

→怒られる。

スクリーンショット 2016-08-21 2.43.47.png

caseB. iPhone5s以降の端末

→怒られない。

スクリーンショット 2016-08-21 2.45.04.png

指定子%ldを使用した場合は、

32bit端末では怒られるが、64bit端末では怒られない

ということですね。

1.の%dを使用した場合と2.の%ldを使用した場合ともに

「引数にNSIntegerを使用すべきでない。明示的にlongにキャストすべきだよ。」

というような警告が出ています。

警告の通りlongにキャストしてあげれば、32bit端末でも64bit端末でも怒られません。

スクリーンショット 2016-08-20 12.13.15.png


警告の意味を探る

警告の意味を探るためにNSIntegerがどんなヤツなのか覗いてみました。

スクリーンショット 2016-08-20 11.30.09.png

32bitの場合は、

typedef int NSInteger;

typedef unsigned int NSUInteger;

64bitの場合は、

typedef long NSInteger;

typedef unsigned long NSUInteger;

という定義でした。

先ほど確認した警告は、

32bit端末, 64bit端末ではNSIntegerの定義が違うため発生していた

ということですね。

長たらしくて現実的に使うことは無いですが、

以下のように書いてみたら32bit端末,64bit端末ともに

警告無く、ログ出力できました。

NSInteger hoge = 100;

#ifdef __LP64__
NSLog(@"[64bit] output hoge = %ld", hoge);
#else
NSLog(@"[32bit] output hoge = %d", hoge);
#endif


書式%zdで32bit, 64bit両方に対応

警告の原因はわかりました。

続いて、明示的にキャストしなくても32bit, 64bit両方に対応できるという%zdとは何者なのか整理してみます。

%zdは、

指定子%d(符号付き32bit整数)に

長さ修飾子z(size_tまたはこれに類する符号つき整数であることを指定)

をくっつけたものです。

(文字列操作プログラミングガイドのp16~ あたりを参考にしました。)

size_tってヤツは、

(size_t)sizeof(NSInteger)

こんな感じで取得できるメモリサイズのことですね。

NSInteger, int, long, size_tのメモリサイズを

sizeof( )で見てみましょう。

caseA. 32bit端末

スクリーンショット 2016-08-21 1.49.21.png

caseB. 64bit端末

スクリーンショット 2016-08-21 1.50.47.png

NSIntegerの定義が32bit端末の場合int型, 64bit端末の場合long型なので、

32bitの場合

NSIntegerのメモリサイズ = intのメモリサイズ = 4

64bitの場合

NSIntegerのメモリサイズ = longのメモリサイズ = 8

でした。

加えて、32bitの場合, 64bitの場合ともに

NSIntegerのメモリサイズ = size_tのメモリサイズ

ということを確認できました。

だから、以下のように書式%zdを使用すれば32bit, 64bit両方に対応できるといった感じですね。

NSInteger hoge = 100;

NSLog(@"output hoge = %zd", hoge);


さいごに

確認したことをいろいろ書いたら長くなってしまいましたが一応整理できました。

文字列操作プログラミングガイド

こちらをもっと早くに読んでおけばよかったなーと思いました。