Alternatives to type casting when formatting NS(U)Integer on 32 and 64 bit architectures?
With the 64 bit version of iOS we can't use %d
and %u
anymore to format NSInteger
and NSUInteger
. Because for 64 bit those are typedef'd to long
and unsigned long
instead of int
and unsigned int
.
So Xcode will throw warnings if you try to format NSInteger with %d. Xcode is nice to us and offers an replacement for those two cases, which consists of a l-prefixed format specifier and a typecast to long. Then our code basically looks like this:
NSLog(@"%ld", (long)i);
NSLog(@"%lu", (unsigned long)u);
Which, if you ask me, is a pain in the eye.
A couple of days ago someone at Twitter mentioned the format specifiers %zd
to format signed variables and %tu
to format unsigned variables on 32 and 64 bit plattforms.
NSLog(@"%zd", i);
NSLog(@"%tu", u);
Which seems to work. And which I like more than typecasting.
But I honestly have no idea why those work. Right now both are basically magic values for me.
I did a bit of research and figured out that the z
prefix means that the following format specifier has the same size as size_t
. But I have absolutely no idea what the prefix t
means. So I have two questions:
What exactly do %zd
and %tu
mean?
And is it safe to use %zd
and %tu
instead of Apples suggestion to typecast to long?
I am aware of similar questions and Apples 64-Bit Transition guides, which all recommend the %lu (unsigned long)
approach. I am asking for an alternative to type casting.
From http://pubs.opengroup.org/onlinepubs/009695399/functions/printf.html:
-
z
Specifies that a following [...] conversion specifier applies to asize_t
or the corresponding signed integer type argument; -
t
Specifies that a following [...] conversion specifier applies to aptrdiff_t
or the corresponding unsigned type argument;
And from http://en.wikipedia.org/wiki/Size_t#Size_and_pointer_difference_types:
-
size_t
is used to represent the size of any object (including arrays) in the particular implementation. It is used as the return type of thesizeof
operator. -
ptrdiff_t
is used to represent the difference between pointers.
On the current OS X and iOS platforms we have
typedef __SIZE_TYPE__ size_t;
typedef __PTRDIFF_TYPE__ ptrdiff_t;
where __SIZE_TYPE__
and __PTRDIFF_TYPE__
are predefined by the
compiler. For 32-bit the compiler defines
#define __SIZE_TYPE__ long unsigned int
#define __PTRDIFF_TYPE__ int
and for 64-bit the compiler defines
#define __SIZE_TYPE__ long unsigned int
#define __PTRDIFF_TYPE__ long int
(This may have changed between Xcode versions. Motivated by @user102008's comment, I have checked this with Xcode 6.2 and updated the answer.)
So ptrdiff_t
and NSInteger
are both typedef'd to the same type:
int
on 32-bit and long
on 64-bit. Therefore
NSLog(@"%td", i);
NSLog(@"%tu", u);
work correctly and compile without warnings on all current iOS and OS X platforms.
size_t
and NSUInteger
have the same size on all platforms, but
they are not the same type, so
NSLog(@"%zu", u);
actually gives a warning when compiling for 32-bit.
But this relation is not fixed in any standard (as far as I know), therefore I would
not consider it safe (in the same sense as assuming that long
has the same size
as a pointer is not considered safe). It might break in the future.
The only alternative to type casting that I know of is from the answer to "Foundation types when compiling for arm64 and 32-bit architecture", using preprocessor macros:
// In your prefix header or something
#if __LP64__
#define NSI "ld"
#define NSU "lu"
#else
#define NSI "d"
#define NSU "u"
#endif
NSLog(@"i=%"NSI, i);
NSLog(@"u=%"NSU, u);
I prefer to just use an NSNumber
instead:
NSInteger myInteger = 3;
NSLog(@"%@", @(myInteger));
This does not work in all situations, but I've replaced most of my NS(U)Integer formatting with the above.
According to Building 32-bit Like 64-bit, another solution is to define the NS_BUILD_32_LIKE_64
macro, and then you can simply use the %ld
and %lu
specifiers with NSInteger
and NSUInteger
without casting and without warnings.