NSLog/printf specifier for NSInteger?
Solution 1
Updated answer:
You can make use of the z
and t
modifiers to handle NSInteger
and NSUInteger
without warnings, on all architectures.
You want to use %zd
for signed, %tu
for unsigned, and %tx
for hex.
This information comes courtesy of Greg Parker.
Original answer:
The official recommended approach is to use %ld
as your specifier, and to cast the actual argument to a long
.
Solution 2
The accepted answer is absolutely reasonable, it is standard conforming, and correct. The only problem is that it doesn't work anymore, which is completely Apple's fault.
The format %zd is the C/C++ standard format for size_t and ssize_t. Like NSInteger and NSUInteger, size_t and ssize_t are 32 bit on a 32 bit system, and 64 bit on a 64 bit system. And that's why printing NSInteger and NSUInteger using %zd worked.
However, NSInteger and NSUInteger are defined as "long" on a 64 bit system, and as "int" on a 32 bit system (which is 64 vs 32 bit). Today, size_t is defined on "long" on all systems, which is the same size as NSInteger (either 64 or 32 bit), but a different type. Either Apple's warnings have changed (so it doesn't allow passing the wrong type to printf, even though it has the right number of bits), or the underlying types for size_t and ssize_t have changed. I don't know which one, but %zd stopped working some time ago. There is no format today that will print NSInteger without warning on both 32 and 64 bit systems.
So the only thing you can do unfortunately: Use %ld, and cast your values from NSInteger to long, or from NSUInteger to unsigned long.
Once you don't build for 32 bit anymore, you can just use %ld, without any cast.
Steven Fisher
Have been programming mobile (iOS and Android) full time since March 2009. I use SQLite a lot. Historical experience in many more languages and environments, especially C/C++ and Borland Pascal/Delphi. I have an infrequently updated blog and a Twitter feed that gets updated with any old thing that pops into my head.
Updated on January 26, 2020Comments
-
Steven Fisher over 4 years
A
NSInteger
is 32 bits on 32-bit platforms, and 64 bits on 64-bit platforms. Is there aNSLog
specifier that always matches the size ofNSInteger
?Setup
- Xcode 3.2.5
- llvm 1.6 compiler (this is important; gcc doesn't do this)
GCC_WARN_TYPECHECK_CALLS_TO_PRINTF
turned on
That's causing me some grief here:
#import <Foundation/Foundation.h> int main (int argc, const char * argv[]) { @autoreleasepool { NSInteger i = 0; NSLog(@"%d", i); } return 0; }
For 32 bit code, I need the
%d
specifier. But if I use the%d
specifier, I get a warning when compiling for 64 bit suggesting I use%ld
instead.If I use
%ld
to match the 64 bit size, when compiling for 32 bit code I get a warning suggesting I use%d
instead.How do I fix both warnings at once? Is there a specifier I can use that works on either?
This also impacts
[NSString stringWithFormat:]
and[[NSString alloc] initWithFormat:]
. -
Steven Fisher over 13 yearsThis is definitely the way to go, but I think I might use
static inline NSIntToLong(NSInteger i) {return (long)i;}
. This avoids disabling type checking completely (i.e. if the type of i changes). -
Erik over 12 yearsGood thinking by @steven-fisher. Avoid warning with:
static inline long NSIntToLong(NSInteger i) {return (long)i;}
-
orkoden over 10 yearsYou can also create an NSNumber and log that.
NSLog(@"%@",@(mynsint));
stackoverflow.com/questions/20355439/… -
orkoden over 10 years@KevinBallard This should not be a serious performance issue. You should not use lots of NSLog in production code anyway. If you have to log lots of stuff for some reason, do it on a separate thread.
-
Lily Ballard over 10 years@orkoden: That's no excuse for suggesting a habit of using unnecessarily underperforming code. Using the correct printf token is just as easy as wrapping your number in an
NSNumber
. -
orkoden over 10 years@KevinBallard the problem with doing an explicit cast is, that you can run into issues when the type you cast to is incorrect. E.g. you change the type of an enum from signed to unsigned or an int to a float. All logs of that variable will be wrong because of the cast. If you use NSNumber, it will always be correct.
-
Lily Ballard over 10 years@orkoden: With my updated answer, no more casts are involved.
-
Richard Brightwell about 10 years@KevinBallard I'm concerned about the updated solution as this appears to be mere coincidence and could easily break in a future release. So far as I know, nothing ties size_t to NSInteger size in a permanent way.
-
Lily Ballard about 10 years@RichardBrightwell:
size_t
andlong
are the same size (on all Apple platforms).NSInteger
is tied tolong
(well, it'sint
on 32-bit for backwards compatibility reasons, but on 32-bitint
andlong
are the same size). -
Steven Fisher over 9 yearsLove the new answer. :)
-
Alex Cio over 9 yearsPerfect solution. Since I use
NSInteger
I always cast it to long(long)
and define the placeholder as %ld.... but its a mess to write it every time again -
Steven Fisher over 8 yearsNot to mention
(long)
style casts are inherently unsafe. Ifi
is something other than the expectedNSInteger
, it will (mostly) still end up a long. -
SirDarius about 7 yearsThe official docs say use %ld and cast to long for NSInteger. developer.apple.com/library/content/documentation/Cocoa/… -- anything else is depending on undocumented (or just current) behavior.
-
Lily Ballard about 7 years@LouFranco The official docs do say that, but I don't know why. Using
%zd
or%tu
works perfectly fine and is not relying on undocumented or "happens to work" behavior. The only reason I can think of for preferring casting to long is because on 32-bit macOSNSInteger
andNSUInteger
are defined in terms ofint
instead oflong
, but on all Apple 32-bit platformsint
andlong
are the same size so that's ok. -
SirDarius about 7 yearsThe reason I prefer casting to long is because that is what the official docs say will definitely work and is the most likely thing that may continue working in future versions.
-
Lily Ballard about 7 years@LouFranco I guarantee you 100% the solution in my answer will work for all future versions. What the docs say works, but that doesn't mean the docs's solution is the only thing that works.
-
Rob MacEachern about 6 yearsAs of Xcode 9.3 there is a warning when using NSInteger as a format argument with
%zd
:Values of type 'NSInteger' should not be used as format arguments; add an explicit cast to 'long' instead
-
Lily Ballard about 6 years@RobMacEachern Yeah I already filed a radar about it, because that warning is flat-out wrong.
-
Iulian Onofrei about 6 yearsBut those new flags mentioned in the tweet are not in the documentation. What gives?
-
Iulian Onofrei about 6 years@KevinBallard, But it is relying on undocumented behavior since none of those flags are documented.
-
Iulian Onofrei almost 6 yearsThe question was more about
NSLog
thanprintf
, as the very first question states. -
Lily Ballard almost 6 years@IulianOnofrei
NSLog
supports everything thatprintf
does. It's a strict superset.