Alternatives to type casting when formatting NS(U)Integer on 32 and 64 bit architectures?
Asked Answered
P

3

54

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.

Peraea answered 19/9, 2013 at 11:51 Comment(2)
What about @"%i". I think it will work ,)Thermography
@AugustinePA: %i is exactly equivalent to %d and does not help here. Unfortunately, there is no format specifier for NS(U)Integer.Illfounded
I
51

From http://pubs.opengroup.org/onlinepubs/009695399/functions/printf.html:

  • z
    Specifies that a following [...] conversion specifier applies to a size_t or the corresponding signed integer type argument;
  • t
    Specifies that a following [...] conversion specifier applies to a ptrdiff_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 the sizeof 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);
Illfounded answered 19/9, 2013 at 12:36 Comment(4)
Thank you. The "accidentally the same size" part is basically what I thought when I first heard about zd and tu. I guess I stick with type casting then, the #define stuff looks even worse.Peraea
Additional documentation from Apple: developer.apple.com/library/ios/documentation/cocoa/conceptual/…Pathogen
"Now size_t and ptrdiff_t accidentally have the same size as NS(U)Integer on Apple's current 32-bit and 64-bit architectures." The same thing is true for long/unsigned long, but why is there a warning when using %ld/%lu without a cast? Also, %zu generates a warning even as you say it should work similarly to %tu.Silvio
@user102008: You are right, my argumentation was wrong. The real reason is not that they have the same size, but that ptrdiff_t and NSInteger are both typedef'd to the same type (int or long). I have updated the answer accordingly. And yes, NSLog(@"%zu", u); generates a warning on 32-bit. Perhaps I did not check it correctly, or that changed since then. And I wonder why NSLog(@"%zd", i); does not generate a warning on 32-bit.Illfounded
T
11

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.

Telegraphese answered 25/2, 2014 at 17:43 Comment(1)
I like this and in some cases wouldn't hesitate to use it. Just keep in mind that there is some overhead to creating & destroying the temporary object and its description. Oh, and you lose field width & precision specifiers (for a CGFloat for example).Intreat
S
2

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.

Silvio answered 2/4, 2015 at 22:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.