Format String Issue Using NSInteger

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);

Integer Sizes

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.

Length Modifiers

This Stack Overflow answer was brought to my attention by Johannes Stühler on Twitter. It makes use of the z and t length modifiers described as follows in the String Programming Guide:

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 %tu.

NSNumber Literals

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).