NSLog/printf specifier for NSInteger? - objective-c

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:].

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.

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.

The formatters come from the standard UNIX/POSIX printf function. Use %lu for unsigned long, %ld for long, %lld for long long, and %llu for unsigned long long. Try man printf on the console, but on Mac it is incomplete. The linux manpages are more explicit http://www.manpages.info/linux/sprintf.3.html
Both warnings can only be fixed by NSLog(#"%lu", (unsigned long)arg); combined with a cast as the code will be compiled in 32 AND 64 bit for iOS. Otherwise each compilation creates a separate warning.

Related

String formatting %ld for NSInteger suggests (long)? [duplicate]

This question already has answers here:
NSInteger and NSUInteger in a mixed 64bit / 32bit environment
(3 answers)
Closed 8 years ago.
When I use %d with an NSInteger, Xcode suggests using %ld and casting the NSInteger with (long). But when I just use %ld without the (long)myNSInteger, the warning goes away.
Which should I use?
From Apple's headers:
#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
So the type of NSInteger is either int or long, depending on the target platform. You're not getting a warning when you don't cast because you're (most likely) running on a 64 bit platform. If you target a 32bit platform, you'll get a warning without the cast.
You can use %zd, which will always work without the cast on 32bit and 64bit platforms (but only for signed integers—I think).
You can do something like this and not ever worry about it (but there is a performance hit):
NSInteger myNSInteger = 12345;
[NSString stringWithFormat:#"%#", #(myNSInteger)];
Depends on the device in the simulator:
With iPhone 5 or less, NSinteger is 32 bits, and %d is ok, if you use %ld you should cast with (long).
With iPhone 5S or more, NSInteger is 64 bits, and %ld is ok.
The best option to don't have any warning is ld and cast all (long).

can't use array.count any longer

now, when I try to nslog my array count using self.array.count with %d I receive this messageL:Values of type "NSUInteget" Should be not be used as format argumments and it suggest s that I fix it with %lu instead,. is this documented anywhere?
NSArray's count method returns an NSUInteger, as documented. If you're using a 64 bit environment, it's also documented that those require the format of %lu.
If you were using a signed NSInteger instead, you would need to use %ld in a 64 bit environment, or %d in a 32 bit environment.
You are probably seeing the message now because you have updated your compiler, Xcode is doing more checking and issuing more warnings.
The issue here is that the size of NSUInteger (and NSInteger) is different on different platforms. Rather than add a new format specifier to handle this Apple choose to recommend that the format specifier for the largest possible type is used and a cast. So:
For NSUInteger use the format specifier %lu and cast the value to unsigned long; and
For NSInteger use the format specifier %ld and cast the value to long.
Doing this will produce correct results on both 32-bit and 64-bit platforms.
See Platform Dependencies in String Format Specifiers.

In which case I have to use NSInteger and when to use simple int? [duplicate]

When should I be using NSInteger vs. int when developing for iOS? I see in the Apple sample code they use NSInteger (or NSUInteger) when passing a value as an argument to a function or returning a value from a function.
- (NSInteger)someFunc;...
- (void)someFuncWithInt:(NSInteger)value;...
But within a function they're just using int to track a value
for (int i; i < something; i++)
...
int something;
something += somethingElseThatsAnInt;
...
I've read (been told) that NSInteger is a safe way to reference an integer in either a 64-bit or 32-bit environment so why use int at all?
You usually want to use NSInteger when you don't know what kind of processor architecture your code might run on, so you may for some reason want the largest possible integer type, which on 32 bit systems is just an int, while on a 64-bit system it's a long.
I'd stick with using NSInteger instead of int/long unless you specifically require them.
NSInteger/NSUInteger are defined as *dynamic typedef*s to one of these types, and they are defined like this:
#if __LP64__ || TARGET_OS_EMBEDDED || TARGET_OS_IPHONE || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
With regard to the correct format specifier you should use for each of these types, see the String Programming Guide's section on Platform Dependencies
Why use int at all?
Apple uses int because for a loop control variable (which is only used to control the loop iterations) int datatype is fine, both in datatype size and in the values it can hold for your loop. No need for platform dependent datatype here. For a loop control variable even a 16-bit int will do most of the time.
Apple uses NSInteger for a function return value or for a function argument because in this case datatype [size] matters, because what you are doing with a function is communicating/passing data with other programs or with other pieces of code; see the answer to When should I be using NSInteger vs int? in your question itself...
they [Apple] use NSInteger (or NSUInteger) when passing a value as an
argument to a function or returning a value from a function.
OS X is "LP64". This means that:
int is always 32-bits.
long long is always 64-bits.
NSInteger and long are always pointer-sized. That means they're 32-bits on 32-bit systems, and 64 bits on 64-bit systems.
The reason NSInteger exists is because many legacy APIs incorrectly used int instead of long to hold pointer-sized variables, which meant that the APIs had to change from int to long in their 64-bit versions. In other words, an API would have different function signatures depending on whether you're compiling for 32-bit or 64-bit architectures. NSInteger intends to mask this problem with these legacy APIs.
In your new code, use int if you need a 32-bit variable, long long if you need a 64-bit integer, and long or NSInteger if you need a pointer-sized variable.
If you dig into NSInteger's implementation:
#if __LP64__
typedef long NSInteger;
#else
typedef int NSInteger;
#endif
Simply, the NSInteger typedef does a step for you: if the architecture is 32-bit, it uses int, if it is 64-bit, it uses long. Using NSInteger, you don't need to worry about the architecture that the program is running on.
You should use NSIntegers if you need to compare them against constant values such as NSNotFound or NSIntegerMax, as these values will differ on 32-bit and 64-bit systems, so index values, counts and the like: use NSInteger or NSUInteger.
It doesn't hurt to use NSInteger in most circumstances, excepting that it takes up twice as much memory. The memory impact is very small, but if you have a huge amount of numbers floating around at any one time, it might make a difference to use ints.
If you DO use NSInteger or NSUInteger, you will want to cast them into long integers or unsigned long integers when using format strings, as new Xcode feature returns a warning if you try and log out an NSInteger as if it had a known length. You should similarly be careful when sending them to variables or arguments that are typed as ints, since you may lose some precision in the process.
On the whole, if you're not expecting to have hundreds of thousands of them in memory at once, it's easier to use NSInteger than constantly worry about the difference between the two.
On iOS, it currently does not matter if you use int or NSInteger. It will matter more if/when iOS moves to 64-bits.
Simply put, NSIntegers are ints in 32-bit code (and thus 32-bit long) and longs on 64-bit code (longs in 64-bit code are 64-bit wide, but 32-bit in 32-bit code). The most likely reason for using NSInteger instead of long is to not break existing 32-bit code (which uses ints).
CGFloat has the same issue: on 32-bit (at least on OS X), it's float; on 64-bit, it's double.
Update: With the introduction of the iPhone 5s, iPad Air, iPad Mini with Retina, and iOS 7, you can now build 64-bit code on iOS.
Update 2: Also, using NSIntegers helps with Swift code interoperability.
As of currently (September 2014) I would recommend using NSInteger/CGFloat when interacting with iOS API's etc if you are also building your app for arm64.
This is because you will likely get unexpected results when you use the float, long and int types.
EXAMPLE: FLOAT/DOUBLE vs CGFLOAT
As an example we take the UITableView delegate method tableView:heightForRowAtIndexPath:.
In a 32-bit only application it will work fine if it is written like this:
-(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}
float is a 32-bit value and the 44 you are returning is a 32-bit value.
However, if we compile/run this same piece of code in a 64-bit arm64 architecture the 44 will be a 64-bit value. Returning a 64-bit value when a 32-bit value is expected will give an unexpected row height.
You can solve this issue by using the CGFloat type
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}
This type represents a 32-bit float in a 32-bit environment and a 64-bit double in a 64-bit environment. Therefore when using this type the method will always receive the expected type regardless of compile/runtime environment.
The same is true for methods that expect integers.
Such methods will expect a 32-bit int value in a 32-bit environment and a 64-bit long in a 64-bit environment. You can solve this case by using the type NSInteger which serves as an int or a long based on the compile/runtime environemnt.
int = 4 byte (fixed irrespective size of the architect)
NSInteger = depend upon size of the architect(e.g. for 4 byte architect = 4 byte NSInteger size)

Are there any issues with int32_t to NSInteger casting?

I am creating a bitmask for the iOS using the data type int32_t. This is then set to a variable that accepts an NSInteger. This throws no compile time errors as expected, but I was wondering - is there was some way that this could cause run-time errors in the future?
In general, it will work. NSInteger will always be at least 32 bits long. If it is 64 bits, your number will be sign extended to match (you might need to cast it). It will not cause run-time errors.
Detailed info about NSInteger
NSInteger is defined using the following code (from NSObjCRuntime.h):
#if __LP64__ || TARGET_OS_EMBEDDED || TARGET_OS_IPHONE || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
This means that NSInteger is equivalent to long when compiling for 64 bit, an embedded OS, iPhone, Windows, or when building 32 bit like 64 bit. Otherwise, it is equivalent to int. Since int and int32_t are equivalent, you will be OK in 32 bit applications on OS X. For the other situations, it depends on the size of long.
In 64 bit OS X applications, long is a 64 bit number. On the iPhone, long is a 32 bit number. I do not know about Windows, and I am not sure whether it uses the 32 bit or 64 bit long when building 32 bit like 64 bit.

Why does only NSLog warn me about using the %lu string format specifier for NSUInteger?

For some reason, I get a compilation error when I try to do the following:
NSLog(#"row: %lu", indexPath.row);
where row is of type NSUInteger. The error I get is
Conversion specifies type 'unsigned long' but the argument has type 'NSUInteger' (aka 'unsigned int')
I can do the following with no compilation errors:
NSString * string = [NSString stringWithFormat:#"row: %lu", indexPath.row];
I'm using the exact same format string and substitution argument in both cases, but why does NSLog freak out while -stringWithFormat: seems to be perfectly content? My compiler is LLVM 1.6.
All devices that iOS currently runs on are 32-bit. If you want to silence the warning:
NSLog(#"row: %lu", (unsigned long)indexPath.row);
[Edit: As of the iPhone 5s, it is no longer true that iOS is always 32-bit.]
I've run into this same issue, and although #Wevah is correct and his answer works just fine, there's another option that doesn't require any code changes. See the following Apple documentation for details:
String Programming Guide | Platform Dependencies
64-Bit Transition Guide for Cocoa | Building 32-Bit Like 64-Bit
The NS_BUILD_32_LIKE_64 preprocessor macro is quite helpful. You can set it in your Xcode project settings (under GCC_PREPROCESSOR_DEFINITIONS) or just put #define NS_BUILD_32_LIKE_64 1 in your precompiled header (.pch) file. In my app, this eliminated 11 warnings without any code changes.
This works because unsigned int and unsigned long are the same size (4 bytes) on iOS, so changing the typedef of NSUInteger makes the compiler (and the developer) happy, but the hardware doesn't care, since it's just doing integer math in both cases. :-)
Apple documentation recommends casting the 64 bit value to a 32 bit value using %lu and %ld. This poses a problem if you actually use the extra 32 bits. Format strings %qu and %qd specify a 64 bit value (unsigned and signed respectively). If you want code that will compile in either mode, then values declared as NSUInteger or NSInteger will have to be cast to a UInt64 or SInt64 in the parameter list to avoid the warning.