NSLog 格式化输出 NSInteger/NSUInteger

在 Xcode 中 使用 NSLog 打印 NSInteger/NSUInteger 数据, 经常会遇到类似的警告.

如下图所示:
1

但是在 iphone 4s(32位)机器上就没有任何警告, 只是在64位的机型上面有这样的警告.

NSObjCRuntime.h 里面定义 NSIntegerNSUInteger 如下:

1
2
3
4
5
6
7
#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

也就是说 64 位下是 long 类型,32 位下是 int 类型.

所以, 上面的代码在 32 位机型上面不会有警告.

按照 Xcode 的提示, Fix it:

1
2
3
4
5
NSUInteger iun = 3;
NSLog(@"iun: %lu", (unsigned long)iun);
NSInteger nsi = 6;
NSLog(@"nsi: %ld", (long)nsi);

这样无论在 32 位还是在 64 位机型上面, 都没有警告了.

Twitter 帖子

twitter 有个帖子 说了另一种解决办法, 原文如下:

1
%zd, %tu, %tx (signed, unsigned, hex) currently format NSInteger and NSUInteger with no warnings.

意思是使用 zd 可以格式化有符号的如 NSInteger, 使用 tu 格式化无符号的如 NSUInteger. 而 tx 可以用来格式化输出 16 进制数据.

按照这个说法, 修改如下:

1
2
3
4
5
NSUInteger iun = 3;
NSLog(@"iun: %zd", iun);
NSInteger nsi = 6;
NSLog(@"nsi: %td", nsi);

或者这样修改:

1
2
3
4
5
NSUInteger iun = 3;
NSLog(@"iun: %tu", iun);
NSInteger nsi = 6;
NSLog(@"nsi: %td", nsi);

确实没有了警告的问题.

我在实践过程中发现一个问题: 使用 zu 来格式化输出 NSUInteger, 在 32 位机器上面, 仍旧会报警告.

1
2
NSUInteger iun = 3;
NSLog(@"iun: %zu", iun);

这就让我开始怀疑 z 和 t 的可靠性了.

下面接着了解一下它们.

关于 z t

关于 z t 的说明可以参考 String Format Specifiers

1

可以说明:

格式化 z 可以用来代表 size_t.
格式化 t 可以用来代表 ptrdiff_t.

ptrdiff_t 是C/C++标准库中定义的一个与机器相关的数据类型.
ptrdiff_t 类型变量通常用来保存两个指针减法操作的结果, 其定义在 C99 标准中.

size_t 类型用于指明数组长度, 它必须是一个正数, 而 ptrdiff_t 类型则应保证足以存放同一数组中两个指针之间的差距, 它有可能是负数.

即: size_t 是 unsigned 类型, 而 ptrdiff_t 则是 signed 整型.

1
2
printf("size_t bytes = %d\n" ,sizeof(size_t));
printf("ptrdiff_t bytes = %d\n" ,sizeof(ptrdiff_t));

在 32 位机型中输出结果:

1
2
size_t bytes = 4
ptrdiff_t bytes = 4

在 64 位机型中输出结果:

1
2
size_t bytes = 8
ptrdiff_t bytes = 8

这说明, sizet_t 和 ptrdiff_t 是和机器类型相关的跨平台的.

系统定义:

1
2
typedef __SIZE_TYPE__ size_t;
typedef __PTRDIFF_TYPE__ ptrdiff_t;

32 位机型定义:

1
2
#define __SIZE_TYPE__ long unsigned int
#define __PTRDIFF_TYPE__ int

64 位机型定义:

1
2
#define __SIZE_TYPE__ long unsigned int
#define __PTRDIFF_TYPE__ long int

终极解决方案

既然在 Xcode 里面提示 Fix it 可以使用, 那么为了提高代码的健壮性和可维护性, 可以定义宏定义来解决.

宏定义如下:

1
2
3
4
5
6
7
#if __LP64__
#define MZNSI @"ld"
#define MZNSU @"lu"
#else
#define MZNSI @"d"
#define MZNSU @"u"
#endif //__LP64__

使用方法, 如下:

1
2
3
4
NSInteger nsi = 6;
NSUInteger iun = 3;
NSLog(@"NSInteger nsi = %"MZNSI, nsi);
NSLog(@"NSUInteger iun = %"MZNSU, iun);

附录

给出常用的格式化打印输出字符串中的格式符, 如下所示:

1)%c:对应参数是一个 int 类型,但实际运行时会将该 int类型对象转换为 unsigned char 类型.

2)%d 或者 %i:对应参数是一个 int 类型. 只不过, %d 一般用于十进制, %i 可以对应各种进制的数据.

二者在打印输出上面没有什么太大的差别. 但是在接收输入(scanf)上, %d 用于十进制, %i 对应各种进制的数据. 可以参考 Difference between %d and %i format specifier in C language 这篇文章.

3)%f:对应参数是一个 double 类型.

4)%ld:对应参数是一个 long int 类型.

5)%s:对应参数是一个 const char* 类型,表示输出一个字符串.

6)%u:对应参数是一个 unsigned int 类型.

7)%zu:对应参数是一个 size_t 类型.

8)%td:对应参数是一个 ptrdiff_t 类型.

9)%x(或 %X):对应参数是一个 int 类型,不过会以十六进制形式输出,其中大于 9 的数字根据字母 x 大小写进行转换,如果是 %x,则大于 9 的数用 a~f 表示;如果是 %X,则用 A~F 表示.

10)%%:输出一个 % 符号.

坚持原创技术分享!