28

I have a fair amount of string format specifiers in NSLog / NSAssert etc. calls which use %d and %u with NSInteger (= int on 32bit) and NSUInteger (= unsigned int on 32bit) types respectively.

When converting the app to 64bit, this gives warnings (of course), as %ld %lu is expected for what now became a long and unsigned long type.

Simply converting the format specifiers will of course introduce the reverse warnings in the 32bit build.
So the only solution I see to become warning free is using the 64bit specifiers, and casting to the 64bit value types everywhere a warning is given in the 32bit build.

But I was wondering if perhaps there are format specifiers specifically for the NSInteger and NSUInteger type which would work on both architectures without casting?

Pieter
  • 17,435
  • 8
  • 50
  • 89

3 Answers3

61

I think the safest way is to box them into NSNumber instances.

NSLog(@"Number is %@", @(number)); // use the highest level of abstraction

This boxing doesn't usually have to create a new object thanks to tagged pointer magic.

If you really don't want to use NSNumber, you can cast primitive types manually, as others suggested:

NSLog(@"Number is %ld", (long)number); // works the same on 32-bit and 64-bit
ilya n.
  • 18,398
  • 15
  • 71
  • 89
  • 1
    Why is this "safer" than a cast to long? – Martin R Dec 03 '13 at 21:58
  • 6
    If you insert an explicit cast of `NSUInteger` to `unsigned long` but change it to `NSInteger`, you get incorrect behavior for negative values with no warnings. `@()` is guaranteed to work in all cases. – ilya n. Dec 03 '13 at 22:03
  • 1
    And if you cast `NSUInteger` to `long`, you break in cases where `number > ULONG_MAX` (admittedly, this is theoretical, but still, not beautiful). – ilya n. Dec 03 '13 at 22:07
  • @MartinR I think ilya has a point here: Log statements are often quickly introduced for debugging purpose, without considering passed types too much. If there's a way that lets me write the log statement with less thinking, it's a win. – Nikolai Ruhe Dec 05 '13 at 09:37
  • 1
    @NikolaiRuhe: Yes, I do see the advantage now. I am still reluctant to create an object just for printing an integer, and you lose some features (such as specifying a width %10d or %x for hex output). But yes, for debugging this is a good alternative. – Martin R Dec 05 '13 at 09:57
  • 1
    @NikolaiRuhe, tagged pointers are cheap I think (no malloc). Good point about `%10d`. Hex - how about `[NSData dataWithBytes:&number length:sizeof(number)]`? – ilya n. Dec 05 '13 at 14:02
  • After attending the Apple iOS7 Tech Talks, they specifically recommended the casting of these types to (long) and using the appropriate format specifiers when writing code for both 32-bit and 64-bit. This is covered in more detail: https://developer.apple.com/tech-talks/videos/ See the 'Architecting Modern Apps Part 2' – Tim Jan 09 '14 at 10:36
38

You can also use %zd (NSInteger) and %tu (NSUInteger) when logging to the console.

NSInteger integer = 1;
NSLog(@"first number: %zd", integer);

NSUInteger uinteger = 1;
NSLog(@"second number: %tu", uinteger);

Also to be found here.

Community
  • 1
  • 1
d0m1n1k
  • 566
  • 5
  • 10
  • 1
    +1 This should be the best. From wikipedia z: For integer types, causes printf to expect a size_t-sized integer argument. – superarts.org Jul 21 '14 at 07:24
4

No, (unfortunately) there is no printf format that directly corresponds to NS(U)Integer. So for architecture independent code, you have to convert everything to the "long" variant (as the Xcode "Fix-it" suggests):

NSInteger i = ...;
NSLog(@"%ld", (long)i);

The only alternative that I know of is from Foundation types when compiling for arm64 and 32-bit architecture:

// In the precompiled header file:
#if __LP64__
#define NSI "ld"
#define NSU "lu"
#else
#define NSI "d"
#define NSU "u"
#endif

NSInteger i = ...;
NSLog(@"i=%"NSI, i);

using preprocessor macros (but even the author of that answer calls it a "admittedly awful approach").

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382