NSLog/printf specifier for NSInteger?

78,660

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.

Share:
78,660
Steven Fisher
Author by

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, 2020

Comments

  • Steven Fisher
    Steven Fisher over 4 years

    A NSInteger is 32 bits on 32-bit platforms, and 64 bits on 64-bit platforms. Is there a NSLog specifier that always matches the size of NSInteger?

    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
    Steven Fisher over 13 years
    This 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
    Erik over 12 years
    Good thinking by @steven-fisher. Avoid warning with: static inline long NSIntToLong(NSInteger i) {return (long)i;}
  • orkoden
    orkoden over 10 years
    You can also create an NSNumber and log that. NSLog(@"%@",@(mynsint)); stackoverflow.com/questions/20355439/…
  • orkoden
    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
    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
    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
    Lily Ballard over 10 years
    @orkoden: With my updated answer, no more casts are involved.
  • Richard Brightwell
    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
    Lily Ballard about 10 years
    @RichardBrightwell: size_t and long are the same size (on all Apple platforms). NSInteger is tied to long (well, it's int on 32-bit for backwards compatibility reasons, but on 32-bit int and long are the same size).
  • Steven Fisher
    Steven Fisher over 9 years
    Love the new answer. :)
  • Alex Cio
    Alex Cio over 9 years
    Perfect 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
    Steven Fisher over 8 years
    Not to mention (long) style casts are inherently unsafe. If i is something other than the expected NSInteger, it will (mostly) still end up a long.
  • SirDarius
    SirDarius about 7 years
    The 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
    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 macOS NSInteger and NSUInteger are defined in terms of int instead of long, but on all Apple 32-bit platforms int and long are the same size so that's ok.
  • SirDarius
    SirDarius about 7 years
    The 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
    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
    Rob MacEachern about 6 years
    As 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
    Lily Ballard about 6 years
    @RobMacEachern Yeah I already filed a radar about it, because that warning is flat-out wrong.
  • Iulian Onofrei
    Iulian Onofrei about 6 years
    But those new flags mentioned in the tweet are not in the documentation. What gives?
  • Iulian Onofrei
    Iulian Onofrei about 6 years
    @KevinBallard, But it is relying on undocumented behavior since none of those flags are documented.
  • Iulian Onofrei
    Iulian Onofrei almost 6 years
    The question was more about NSLog than printf, as the very first question states.
  • Lily Ballard
    Lily Ballard almost 6 years
    @IulianOnofrei NSLog supports everything that printf does. It's a strict superset.