2014-06-30 11:12:33 h289962920 阅读数 238

苹果于2013年9月推出了iPhone 5S新手机,采用的全新A7处理器其最大特色就是支持64位运算。其64位A7处理器的使用意味着iPhone性能会大有提高,性能和速度更加出色;而要到达到这样的性能,开发者就要开发64位的应用了。

一、讨论宏观问题

  1. 1

    Xcode 5编译的iOS 7程序包含了32位和64位两套二进制代码,在32位的iOS系统上会调用32位的二进制代码,在64位系统上会调用64位的二进制代码,以此来解决向后兼容的问题。同时,考虑到很多32位的程序可能在没有重新编译的情况下部署到64位系统上,64位的iOS系统中带有两套FrameWork,一套是32位的,一套是64位的。当64位的iOS系统运行原来的32位程序时,系统会调用32位的FrameWork作为底层支撑,当系统运行64位程序时,系统会调用64位的FrameWork作为底层支撑。也就是说,当一个iPhone 5S上同时运行32位程序和64位程序时,系统同时将32位和64位两套FrameWork载入了内存中,所以消耗的内存也比较多。如果一台64位的iOS设备上运行的所有程序都是为64位系统编译过的,iOS系统将只载入64位的FrameWork,这将节省好多内存。所以,如果大家都可以快速将程序传换成64位的,iOS将跑得更快。

    END

二、讨论细节问题

  1. 1

    32位的iOS系统和64位的iOS系统主要的差别有两个,一个是数据类型的差别,一个是过程调用方法的差别。在数据类型上,主要的变化是指针类型(Pointer)和长整数类型(long)的长度变化和内存对齐方式的变化,同时也导致了更高级别数据类型的变化,如NSInteger的长度也有变化。在过程调用方法上,因为ARM V8 和ARM V7具有不同数量的寄存器,具有不同的过程调用约定,所以32位系统和64位系统在汇编层级是不同的。

    END

总结:

  • 开发人员根据以下要点来检查原来的32位代码就可以将应用移植到64位系统上了:1. 不要将长整型数据(long)赋予整型(int)这种代码在32位系统上没有问题,因为在32位系统中long和int的长度是一样的,不过在64位系统中就有可能出问题,因为64位系统中long比int长,将long值赋予int将导致数据丢失。2. 不要将指针类型(Pointer)赋予整型(int)为 了方便地址计算,有时程序员会将指针类型赋予整型,这种代码在32位系统上没有问题,因为在32位系统中Pointer和int的长度是一样的,不过在 64位系统中就会有问题,因为64位系统中Pointer比int长,将Pointer值赋予int将导致地址数据丢失,最终导致严重问题。3. 留意那些和数位相关的数值计算比如掩码技术,如果使用一个long类型的掩码,转到64位系统后高位都是0,计算出来的结果可能不符合预期。还有无符号整数和有符号整数的混用等。4. 留意对齐方式带来的变化如果在32位系统上定义一个结构包含两个long类型,第二个long数值的偏移地址是4,可以通过结构地址+4的方式获取,但是在64位系统上就不行了,因为在64位系统中第二个long数值的偏移地址是8。5. 充分考虑在32位应用和64位应用之间的数据交换因 为用户会通过网络交换数据,同时用户保存的数据也可能通过备份等方式在32位系统和64位系统之间切换,所以应用在保存和发送流数据的时候一定要考虑充 分。比如数据在32位系统中保存,在64位系统中能否正常打开,或者反过来,在64位系统中保存,在32位系统中打开是否正常。6. 重写所有汇编代码这点无需说明,如果你在代码中嵌入了汇编代码,你需要参考64位系统的指令集重写汇编代码。7. 不要将可变参数的过程强制转换为定参过程,也不要将定参过程强制转换为可变参数的过程这时因为32位系统和64位系统对于这两种过程调用方式的处理方法不同。

    END

将以前的应用编译成64位程序

  1. 1

    1. 使用Xcode 5 打开原有项目。

    2. 将支持的设备改成“iOS 7”。

    3. 在“Build Setting”中将“Architectures”改成“Standard Architectures (including 64-bit)”。

    4. 运行测试程序,解决编译过程出现的问题。

2016-07-26 22:43:19 wu__di 阅读数 6865

  众所周知,苹果是从iPhone 5s开始对全线移动产品使用64位架构,那么如果App需要兼容32位和64位的机型,就需要注意它们之间的区别。

  下面我们首先看看基本数据类型的区别:

32位编译器

char :1个字节

char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)

short int : 2个字节

int:  4个字节

unsigned int : 4个字节

float:  4个字节

double:   8个字节

long:   4个字节

long long:  8个字节

unsigned long:  4个字节


64位编译器

char :1个字节

char*(即指针变量): 8个字节

short int : 2个字节

int:  4个字节

unsigned int : 4个字节

float:  4个字节

double:   8个字节

long:   8个字节

long long:  8个字节

   从上面的比较我们可以看的出来,对于32位的机器来说,long是四个字节,而对于64位机器的long是8字节,如果在项目开发过程中忽略了这个点,很容易出现问题。下面举例分析:

   在我们的项目中,有这样的需求,服务器端返回一个时间戳,单位为毫秒,客户端需要解析该字段并转化为NSDate,存储到数据库中。对64位的机器我们只需要定义一个字段,比如startTime,类型为long,解析字段可以使用objectForKey的longValue获取到时间戳,然后转换为时间类型,没有任何问题。但是在32位,我们发现转换后的时间类型是错误的,类型1970...。究其原因,就是因为32位机器上毫秒级的时间戳longValue后越界,导致后续转换都异常。解决这类问题的方法是在涉及到时间戳为毫秒的情况下,定义相应字段是long long类型,通过longLongValue获取到值,就不存在溢出的情况。

   如果这偏文章能给你带来启发请关注我其他的文章,或者搜索微信号:785798889关注我,谢谢~




2017-08-02 23:47:45 a997013919 阅读数 1346

1.首先说32位与64位的问题

之前不清楚iOS是32位还是64位的时候,看见sizeof(int) 打印出来的结果是4的时候,就单纯地以为是32位的系统,即便是看到Mac的“关于本机”里的内存大小清清楚楚地写着是16G(64位OS内存一般为16G,即2的64次方),我也天真地以为大概macOS 和iOS 两者位宽大概不一样吧。因为楼主以前学Linux的,我清清楚楚地记得老师说:int 整型 的“整”体现在它与CPU本身的位宽一样。那iOS是基于Unix,Linux又是高仿的Unix,我就以为一样啊....所以是一直都是这么测的啊,直到,脑抽直接把指针传给sizeof,打印出来是8,

        char *p = NULL;
	printf("------- sizeof(p) = %ld ----\n", sizeof(p));

打印结果:

    ------- sizeof(p) = 8 ----


8字节,那为什么int只有4字节?凌乱....

然后在网上好一顿搜索,确实也找到了一些资料:(来自iOS32位和64位的坑  这篇文章挺好)


对于iOS设备来说

  • 32位编译器

char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节,最大是2147483648,再大则溢出
long long: 8个字节
unsigned long: 4个字节

  • 64位编译器

char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 8个字节,最大是18446744073709552000
long long: 8个字节
unsigned long: 8个字节

比较得知,32与64区别主要在long,64位比32位大得多。 (这部分都是粘贴自iOS32位和64位的坑  这篇文章)

那现在已经明白了,目前在用的是64位系统,跟Linux毕竟还是不一样,不纠结这个问题了,继续下一个。



2.其次是结构体对齐访问的问题

前面既然已经说了是64位,可是新疑问又来了

typedef struct my_Header
{
    char    msgHead[4];
    short   tag;
    int     length;
    
}My_Header;






My_Header myHeader;
memset(&myHeader, 0, sizeof(myHeader));

printf("------- head len = %d ----\n", sizeof(myHeader));

按照结构体的对齐访问,OS的位宽又是8字节,所以猜测打印出来的结果应该是16字节,...
但是实际打印的是12字节,

这又是怎么肥事?要不是找到了上面的那篇资料,我真的又要以为iOS是32位的了,网上搜了好多,基本也是讲32位下的4字节对齐访问方式。

既然说到结构体的对齐访问,不如一起来复习一下,(以下内容都是基于Linux的32位平台下的,iOS平台下的内容有待考究,不过可以参考)

结构体的对齐访问:

What:

struct header
{
    char  a;
    int   b;
};
struct header haed;
在这个结构体中,打印输出该结构体的字节,发现它占了8个字节。char a 本应是1字节,但实际在这个结构体中,占4字节,这就是“对齐访问”。

Why:为何要对齐访问

1.硬件限制
2.提高效率:
关于效率这方面,我们要从汇编语言这方面切入理解,比较对齐与不对齐访问:
1)对齐访问:
如上图,此时的b,通过一次ldr指令,即可访问到
2)非对齐访问:
如下图
则读取b的步骤变成了:
1. 读取这3个字节

2.读取这一个字节

3.结合

综上,1. 速度降低了2/3,所以效率低。

           2. 对齐访问是牺牲了内存空间,换取了速度。

How:

编译器默认是4字节对齐(32bit),可以通过设置对齐指令来更改对齐方式(1字节对齐、4、8、128字节对齐等)
#prama pack(n)  // 开头。n = 1, 4, 8...
  ......
  ......
#prama pack()    // 结尾


现在再来看看 sizeof(myHeader) 值为 12这事,还是觉得像4字节对齐。不如再给myHeader加一个类型为long的成员:
typedef struct my_Header
{
    char    msgHead[4];
    short   tag;
    int     length;
    long    test;   

}My_Header;
My_Header myHeader;
long为8字节,按照上面所讲,如果结构体是4字节对齐:
(图误,应该和下一张图是一样的)

应该得出结构体所占大小为20字节;

如果是8字节对齐,则:

结构体所占大小为24字节

既然之前sizeof(myHeader) 值为 12,暂时认为是4字节对齐吧,那么按照刚才的分析,结构体所占大小应该为20字节

我们来实际运行看一看
……
------- head len = 24 ----

这下怎么圆回来?前面已经说了那么多
这要怎么理解?此处暂时存疑吧……

我已经尽我最大努力画图了,看不看得懂全凭缘分了。
不过4字节对齐,8毕竟也是它的倍数,按刚刚的思路,仿佛是不影响…也没有找到什么确切的文章,只能理解成它是默认按4字节对齐的吧。……




======================= 8月3日,更新  =======================
其实刚刚最后一张图画完,就已经有点思路了,倒数第二张画的图有误,因为之前说了是在64位的条件下,所以画成4字节的位宽是不对的。
重新更正为:
如果结构体是4字节对齐,情况和下面一张图是一样的。
同理,
typedef struct my_Header
{
    char    msgHead[4];
    short   tag;
    int     length;
    
}My_Header;
结构体大小为12也可以被理解:
如果真的是8字节对齐访问的话,大小应该为8的倍数,所以可以判断OS并不是按8字节对齐的。
想要验证也很简单,用上面刚刚说的#prama pack(8)测试一下,可以得到结果为16 。


总结:

iOS情况下,是位宽为8字节,结构体对齐方式为按4字节对齐。



2015-02-15 09:27:54 Apple_app 阅读数 2081

终究还是来了。Apple下发了支持64位的最后通牒: 

As we announced in October, beginning February 1, 2015 new iOS apps submitted to the App Store must include 64-bit support and be built with the iOS 8 SDK. Beginning June 1, 2015 app updates will also need to follow the same requirements. 

早应该做的适配终于要开始动工了,苦了64位的CPU运行了这么久32位的程序。前段时间公司项目完成了64-bit包的适配,本没那么复杂的事被无数不标准的老代码搅和的不轻,总结几个Tip共勉。 

Tips 

拒绝基本数据类型和隐式转换 

首当其冲的就是基本类型,比如下面4个类型在32-bit和64-bit下分别是多长呢? 

?
1
2
3
4
size_t s1 = sizeof(int);
size_t s2 = sizeof(long);
size_t s3 = sizeof(float);
size_t s4 = sizeof(double);

32-bit下:4, 4, 4, 8;64-bit下:4, 8, 4, 8
(PS: 这个结果随编译器,换其他平台可不一定)
它们的长度变化可能并非我们对64-bit长度加倍的预期,所以说,程序中出现sizeof的代码多看两眼。而且,除非你明确知道自己在做什么,应该使用下面的类型代替基本类型: 

  • int -> NSInteger 
  • unsigned -> NSUInteger 
  • float -> CGFloat 
  • 动画时间 -> NSTimeInterval 
  • … 

这些都是SDK中定义的类型,而我们大部分时间都在跟SDK的API们打交道,使用它们能将类型转换的影响降低很多。 

再比如说下面的代码: 

?
1
2
3
4
NSArray *items = @[<a href="http://www.jobbole.com/members/1/">@1</a>, @2, @3];
for (int i = -1; i < items.count; i++) {
    NSLog(@"%d", i);
}

结果是,for循环一次都没有进。
数组的count是NSUInteger类型的,-1与其比较时隐式转换成NSUInteger,变成了一个很大的数字: 

?
1
2
3
4
(lldb) p i
(int) $0 = -1
(lldb) p (NSUInteger)i
(NSUInteger) $1 = 18446744073709551615

这和64-bit到没啥关系,想要说明的是,这种隐式转换也需要小心,一定要注意和这个变量相关的所有操作(赋值、比较、转换)
老式for循环可以考虑写成: 

?
1
for (NSUInteger index = 0; index < items.count; index++) {}

当然,数组遍历还是更推荐用for-in或block版本的,它们之间的比较可以回顾下这篇文章。 

使用新版枚举 

和上面的原因差不多,枚举应该使用新版的写法: 

?
1
2
3
4
5
6
typedef NS_ENUM(NSInteger, UIViewAnimationCurve) {
    UIViewAnimationCurveEaseInOut,
    UIViewAnimationCurveEaseIn,
    UIViewAnimationCurveEaseOut,
    UIViewAnimationCurveLinear
};

不仅能为枚举值指定类型,而且当赋值赋错类型时,编译器还会给出警告,没理由不用这种写法。 

替代Format字符串 

适配64-bit时,你是否遇到了下面的恶心写法: 

?
1
2
NSArray *items = @[<a href="http://www.jobbole.com/members/1/">@1</a>, @2, @3];
NSLog(@"数组元素个数:%lu", (unsigned long)items.count);

一般情况下,利用NSNumber的@语法糖就可以解决: 

?
1
2
NSArray *items = @[<a href="http://www.jobbole.com/members/1/">@1</a>, @2, @3];
NSLog(@"数组元素个数:%@", @(items.count));

同理,int转string也可以: 

?
1
2
NSInteger i = 10086;
NSString *string = @(i).stringValue;

当然,如需要%.2f这种Format就不适用了。 

64-bit下的BOOL 

32-bit下,BOOL被定义为signed char,@encode(BOOL)的结果是'c'
64-bit下,BOOL被定义为bool,@encode(BOOL)结果是'B'
更直观的解释是: 

?
1
2
3
4
(lldb) p/t (signed char)7
(BOOL) $0 = 0b00000111 (YES)
(lldb) p/t (bool)7
(bool) $1 = 0b00000001 (YES)

32-bit版本的BOOL包括了256个值的可能性,还会引起一些坑,像这篇文章所说的。而64-bit下只有0(NO),1(YES)两种可能,终于给BOOL正了名。 

不直接取isa指针 

编译器已经默认禁用了这种使用,isa指针在32位下是Class的地址,但在64位下利用bits mask才能取出来真正的地址,若真需要,使用runtime的object_getClass和object_setClass方法。关于64位下isa的讲解可以看这篇文章

解决第三方lib依赖和lipo命令 

以源码形式出现在工程中的第三方lib,只要把target加上arm64编译就好了。
恶心的就是直接拖进工程的那些静态库(.a)或者framework,就需要重新找支持64-bit的包了。这时候就能看出哪些是已无人维护的lib了,是时候找个替代品了(比如我全网找不到工程中用到的一个音频库的64位包,终于在一个哥们的github上找到,哭着给了个star- -) 

打印Mach-O文件支持的架构 

如何看一个可执行文件是不是支持64-bit呢? 

使用lipo -info命令,比如看看UIKit支持的架构: 

?
1
2
3
// 当前在Xcode Frameworks目录
sunnyxx$ lipo -info UIKit.framework/UIKit
Architectures in the fat file: UIKit.framework/UIKit are: arm64 armv7s

想看的更详细的信息可以使用lipo -detailed_info: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sunnyxx$ lipo -detailed_info UIKit.framework/UIKit 
Fat header in: UIKit.framework/UIKit
fat_magic 0xcafebabe
nfat_arch 2
architecture arm64
    cputype CPU_TYPE_ARM64
    cpusubtype CPU_SUBTYPE_ARM64_ALL
    offset 4096
    size 16822272
    align 2^12 (4096)
architecture armv7s
    cputype CPU_TYPE_ARM
    cpusubtype CPU_SUBTYPE_ARM_V7S
    offset 16826368
    size 14499840
    align 2^12 (4096)

当然,还可以使用file命令: 

?
1
2
3
4
sunnyxx$ file UIKit.framework/UIKit 
UIKit.framework/UIKit: Mach-O universal binary with 2 architectures
UIKit.framework/UIKit (for architecture arm64):Mach-O 64-bit dynamically linked shared library
UIKit.framework/UIKit (for architecture armv7s):Mach-O dynamically linked shared library arm

上述命令对Mach-O文件适用,静态库.a文件,framework中的.a文件,自己app的可执行文件都可以打印下看看。 

合并多个架构的包 

如果,我们有MyLib-32.a和MyLib-64.a,可以使用lipo -create命令合并: 

?
1
sunnyxx$ lipo -create MyLib-32.a MyLib-64.a -output MyLib.a

支持64-bit后程序包会变大么? 

会,支持64-bit后,多了一个arm64架构,理论上每个架构一套指令,但相比原来会大多少还不好说,我们这里增加了大概50%,还有听说会增加一倍的。 

一个lib包含了很多的架构,会打到最后的包里么? 

不会,如果lib中有armv7, armv7s, arm64, i386架构,而target architecture选择了armv7s, arm64,那么只会从lib中link指定的这两个架构的二进制代码,其他架构下的代码不会link到最终可执行文件中;反过来,一个lib需要在模拟器环境中正常link,也得包含i386架构的指令。 

Checklist 

最后列一下官方文档中的注意点: 

  • 不要将指针强转成整数 
  • 程序各处使用统一的数据类型 
  • 对不同类型的整数做运算时一定要注意 
  • 需要定长变量时,使用如int32_t, int64_t这种定长类型 
  • 使用malloc时,不要写死size 
  • 使用能同时适配两个架构的格式化字符串 
  • 注意函数和函数指针(类型转换和可变参数) 
  • 不要直接访问Objective-C的指针(isa) 
  • 使用内建的同步原语(Primitives) 
  • 不要硬编码虚存页大小 
  • Go Position Independent

References 

h/Conceptual/CocoaTouch64BitGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40013501-CH1-SW1
http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
http://www.bignerdranch.com/blog/64-bit-smorgasbord/
http://www.bignerdranch.com/blog/bools-sharp-corners/

原文地址:http://www.open-open.com/lib/view/open1420697675843.html

没有更多推荐了,返回首页