本节书摘来自华章出版社《编写高质量代码:改善objective-c程序的61个建议》一 书中的第2章,作者:刘一道,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
在ios 7版本出现之前,应用程序主要都是基于32位的ios运行环境设计的,很少会考虑到要兼容64位的ios运行环境。现在64位的ios运行环境已经出现了。这个时候,在编写应用程序的时候,就不得不考虑了如何确保自己写的应用程序,既能在ios的32位环境下运行又能在64位的环境下运行。
下面就编写兼容ios 32位和64位运行环境的应用程序容易犯的错误,进行逐一介绍,希望能对各位有所帮助。
不要将长整型数据赋予整型
在许多导致编程错误产生因素之中,最为典型的因素莫过于在应用程序的整个代码中,不能使用一贯的数据类型,导致编译应用程序代码时候,产生大量的警告提醒信息。
故此,当调用函数时,要确保接收到的结果与该函数返回的变量的类型相匹配。与接收变量相比,如果返回类型是一个较大的整数,那么该值将会被截断。
下面的代码示例就表现出了此种错误,分配给一个变量时却截断一个返回值。在此代码示例中performcalculation 函数将返回一个长整型。在 32 位运行时中,int 和 long 都是 32 位,即使代码不正确,但也能确保分配为int类型能有效工作。在 64 位运行时中,结果的高32位被分配时,将被丢掉。要保证不出现数据丢失,就应将结果赋给一个长整数 ,确保代码在32位和64位的运行时环境中都能有效运行。
当作为一个参数传递值时,也会出现与上边一样的问题。例如,在下面的示例代码中64位运行时执行的输入参数时被截断。
在下面的代码示例中,在64位运行时中返回值也被截断,因为该函数的返回类型超出返回值的范围。
在上面的这些示例中,都是假定int 和 long 是完全相同的代码,即相同的数据类型,但是ansi c 标准不能确保假设的这些情况都成立。当应用程序运行在64位环境中时,上面的代码就会出现明显错误。在默认编译环境下,编译器会自动启用32位和64位校验机制,一旦一个值被截断,在大部分情况下,编译器将会自动抛出警告。如果编译器没有启用32位和64位校验机制,这时候应该在编译器选项中明确启用它,或者同时选择转化选项,这样就能利用编译器的校验机制,发现更多更详细的潜在错误。
在cocoa touch的应用程序中,查找以下的整数类型并确保正确地使用它们:
在这两个运行时环境中的 fpos_t 和 off_t 的类型都是 64 位的,所以从来没有将它们分配给一个 int 类型。
善用nsinteger来处理32位和64位之间的转换
在编写应用程序时,可以在代码中多用nsinteger来处理数字类型的变量,因为nsinteger可以与ios不同的运行环境兼容。
无论是在运行32位运行环境中,还是在运行在64位环境中,nsinteger的类型的应用贯穿于整个cocoa touch。其在 32 位运行时中是32 位整数,其在64 位运行时中是 64 位整数。所以,当从一个框架的方法接收信息时,它采用一个nsinteger的类型,因此务必使用nsinteger的类型来保存结果。
永远不要假设nsinteger的类型是一个大小相同的int类型,这里有几个关键例子来看看:
转换成nsnumber对象或转换nsnumber对象为其他。
编码和解码数据里,使用的是 nscoder 类。尤其是,编码 nsinteger在64 位的设备上,但将它解码在 32 位的设备上,如果值超过一个 32 位整数的范围,解码方法将会引发异常。
使用 nsinteger 作为框架中定义的常量。特别值得注意的是nsnotfound 常数。它的价值是大于一个int类型的最大范围,所以在的应用程序中截断其价值往往会导致错误的出现。
在64位的代码中,cgfloat的大小改变了,gfloat类型变为一个64位单精度数。作为nsinteger的类型,不能想当然地把cgfloat认为是一个单精度或双精度数类型。因此,要使用一致的cgfloat。下面的示例就是使用core foundation来创建 cfnumber的。
创建数据结构要注意固定大小和对齐
当数据在32位和64位版本的应用程序之间共享时,就有必要确保创建兼容32位和64位的数据结构是完全相同的。当数据存储在一个文件或在一个传输网络的设备中时,其运行环境可能是相对立的。例如,用户把数据备份存储在32位的设备中,却在64位的设备中进行数据恢复,环境的差异性,在一定的程度上,会影响数据的正常使用,故此,对于这样的数据互操作存在的问题,必须要找到解决的方法。
(1)使用明确的整数数据类型
不管底层的硬件结构,c99标准提供了内置的数据类型都是特定大小的。当数据必须是一个固定大小时,或者当知道一个特定的变量有一个有限的可能值范围时,这个时候就应该考虑使用这些数据类型。通过选择适当的数据类型,会得到一个固定宽度的类型,可以存储在内存中,也避免分配一个变量时浪费内存,其范围远大于需要。
表 2-1列出每个 c99 类型和范围的允许值。

(2)对齐64位整数类型时要小心
在64位运行时,所有的64位整数类型的变化从4字节到8字节对齐。即使明确指定每个整数类型,这两种结构仍然可能是不相同的两个运行时。在代码清单2-1中,对齐改变,即使该字段声明明确的整数类型。
代码清单2-1 对齐的64位整数结构
当使用32位的编译器编译此代码时,字段栏(field bar)是从结构起始开始的12字节;当使用 64 位编译器编译该代码时,字段栏(field bar)是从结构起始的16字节开始的。在foo2中填充4字节,就可以确保foo2的栏(bar)具有8字节,从而实现边界对齐。
如果定义新的数据结构,首先组织的元素具有最大对齐值,最后的是最小的元素。这个结构组织消除正是大多数的填充字节需要的。如果正在使用现有的结构,其中包括未对齐的 64 位整数,可以使用 pragma 来强制其正确对齐。代码清单2-2给出了相同的数据结构,但这里的结构是被迫使用32位对齐规则的。
代码清单2-2 使用的pragma控制对齐
只有在必要时才使用此选项,主要因为访问未对齐的数据,易造成性能上损失。故此要想确保自己的32位版本的应用程序中数据结构,能具有向后兼容性,就有很必要使用此选项。
选择一种紧凑的数据表示形式
在编写应用程序代码时,选择一种恰当的数据结构形式,就可以比较好地表示数据。例如,使用下面的数据结构存储日历日期:
这种结构的长度为24字节;在64位运行时,它需要48字节,只为一个日期!一个更紧凑的表示方式是以秒数来存储某一特定时间。必要时,将此紧凑的表示形式转换为日历的日期和时间。
对于对齐的数据结构,编译器有时会向其中添加填充物,例如:
这种结构包括 14 字节的数据,但由于填充,它占用的 24 字节的空间。更好的设计方式是对字段进行从大到小的排序来对齐。
要点
(1)不要将长整型数据赋予整型。
(2)利用用nsinteger来处理32位和64位之间的转换。
(3)创建数据结构要注意固定大小和对齐。