Format String Issue Using NSIntegerApr 13, 2014 · 3 minute read
The version of Clang supplied with Xcode has over time added a number of additional warnings for potentially unsafe code. This is generally a good thing but I was a little alarmed recently when I opened a project and found the unit test classes had dozens of warnings:
Format String Issue Values of type ‘NSInteger’ should not be used as format arguments; add an explicit cast to ‘long’ instead
The warnings were all for printf format specifiers used in many of the unit test assert statements. A typical example where both expectedCount and count were of type NSInteger:
XCTAssertTrue(expectedCount==count, @"Expected %d got %d", expectedCount, count);
The release by Apple of iOS 7 and the A7 processor also introduced a 64-bit runtime to iOS for the first time. The 64-Bit Transition Guide for Cocoa Touch provides guidelines on how to convert your app to a 64-bit binary. Relevant to my problem it also summarises the changes to the sizes of a number of the built-in data types:
- A long integer changes from 4 to 8 bytes.
- The size of a pointer increases from 4 to 8 bytes.
- NSInteger and NSUInteger Cocoa types increase from 4 to 8 bytes.
- CGFloat increases from a 4 byte float to an 8 byte double.
The cause of the warning in this case comes from the use of a
%d in the format specifier which expects a signed 32-bit integer. However the value supplied was an NSInteger which will be a 64-bit integer with the 64-bit runtime. The fix in this case is to explicitly specify a long integer in the format string (
%ld) and cast the NSInteger to a long:
XCTAssertTrue(expectedCount==count, @"Expected %ld got %ld", (long)expectedCount, (long)count);
In the case of an NSUInteger you would use
%lu and (unsigned long).
Alternatives to Casting (Updated 14 April 2014)
A couple of interesting alternatives for your consideration if you don’t like casting to a long.
z - Length modifier specifying that a following d, o, u, x, or X conversion specifier applies to a size_t or the corresponding signed integer type argument.
t - Length modifier specifying that a following d, o, u, x, or X conversion specifier applies to a ptrdiff_t or the corresponding unsigned integer type argument.
This allows us to write the following and have it be correct for both 32-bit and 64-bit runtimes:
XCTAssertTrue(expectedCount==count, @"Expected %zd got %zd", expectedCount, count);
The equivalent for an NSUInteger would use
Another alternative, which I think I saw first on the Big Nerd Ranch blog is to use the Objective-C literal syntax to convert to an NSNumber object:
XCTAssertTrue(expectedCount==count, @"Expected %@ got %@", @(expectedCount), @(count));
Fix All In Scope
It may also we be worth remembering that if you have a number of such warnings you can get Xcode to fix them all in one go using the “Fix All In Scope” option from the “Editor” menu (keyboard shortcut is ⌃⌥⌘F).