你必须了解的64位编程知识(下篇)

 上文 你必须了解的64位编程知识(上篇)已经讲了些基本的知识,这篇文章开始通过一些例子来更好的呈现。

               指针运算              

64位编程中,可能会有很多指针运算编程需要注意的地方,比如下面处理,

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
short * test(short *ptr){ int n = (int) ptr+ 2;   ptr= (short *) n; return ptr;}

很多时候,因为没有考虑到之后的代码会在64位系统上运行,在指针运算时为了方便,可能会把指针强制类型转换成int类型。这在32位上是没问题的,因为指针在32位系统上和int类型大小都为4byte。但是在64位系统上,int类型是4byte,而指针类型是8 byte,如果做这样的强制类型转换,那么指针的高32 位会被截掉,从而产生错误。

上面的代码经编译,

 

  •  
  •  
ADD   w0, w0, #2   //32位的运算,因为写了W0,会将X0的高32bit 清零,也就是将指针的高32bit清零SXTW  x0, w0       //转换回64 bit,但高32位已经被清零

这是一个比较常见的问题,Google在将Android kernel升级支持64位时,很多的patch就是处理这样的问题,

https://android-review.googlesource.com/c/platform/frameworks/av/+/96922/1/media/libstagefright/codecs/on2/h264dec/omxdl/arm_neon/api/armCOMM.h

 

https://android-review.googlesource.com/c/platform/frameworks/base/+/35211/1/media/libstagefright/codecs/avc/enc/src/avcenc_api.cpp

这样的例子在Android里有很多,所以你如果也这样写过代码,no shame.

解决这个移植的问题的方式,上面patch已经给出:

如果需要对指针进行类型转换,使用intptr_t或uintptr_t类型,如果计算指针的差值,可以使用ptrdiff_t类型。这样写出来的代码可移植到32位和64位系统。

 

       指针运算的考虑       

下面一个例子,

 

  •  
  •  
  •  
  •  
  •  
  •  
int diff_a = -3;unsigned int diff_b =2 ;int * array = (int *)0x80004 ;int *p ; p= array+(a+b);

请问p的值是多少?请大家先花几秒时间想想。

 

 

 

如果你说是0x80003, 这大学C语言课程肯定没及格。

如果你说是0x80000, 在32位系统上是对的。

但是在64位系统上,你还得一步一步按照数据转换规则来,

 

         移位操作        

如果你需要对一个64bit的值的某些位设1,你也许会用这样的代码实现

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
long long val;long long shift(long long a, int bits){ a=a| (1<<bits); return a;} val=shift(val, 36);

 

以上代码是对64bit的val的36bit设1的操作。看出什么问题来了吗?

 

实际上,上面的代码并不正确。还记得前面说的如何对待数值常量吗?

  • 整型常量会被当成是可以表示这个值的最小类型,比如‘8’ 会是 int型

因此以上代码的1会被当成是int类型,对int类型移36位会出现什么样的结果呢?

实际上这个代码会生成如下指令,

 

  •  
  •  
  •  
  •  
MOV   w2, #1LSL    w1, w2, w1 //w1=bits=36SXTW  x1, w1   //将移位值32bit的w1有符号扩展成64bitORR   x0, x0, x1

根据LSL指令的描述,

http://shell-storm.org/armv8-a/ISA_v85A_A64_xml_00bet8/xhtml/lsl_ubfm.html 

 

  •  
  •  
  •  
  •  
  •  
  •  
LSL <Wd>, <Wn>, <Wm>is equivalent toLSLV <Wd>, <Wn>, <Wm> LSLVLogical Shift Left Variable shifts aregister value left by a variable number of bits, shifting in zeros, and writesthe result to the destination register. The remainder obtained by dividing thesecond source register by the data size defines the number of bits by which thefirst source register is left-shifted.

 

  •  
  •  
  •  
  •  
bits(datasize) result;bits(datasize) operand2 = X[m];result = ShiftReg(n,shift_type, UInt(operand2) MOD datasize);X[d] = result;

 

因此移位位数实际上 是(36 MOD 32),也就是左移4位,最后设的其实是bit4而不是bit36.

这个问题的解决方式就是指定‘1’这个常量为long long类型

 

  •  
  •  
  •  
  •  
  •  
long long shift(long long a, int bits){ a=a | (1LL<<bits); return a;}

 

这样生成的代码为,

 

  •  
  •  
  •  
MOV x2,#1LSLx1,x2,x1ORRx0,x0,x1

把1变成64位类型,因此LSL会移位36位,达到了目的。

一个实际driver开发中的例子,

https://patchwork.kernel.org/patch/4680781/ 

 

       位域操作      

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
Struct Test{unsignedshort va:15;unsigned shortvb:13;};Struct Testvc;unsignedlong long vx;int main(){vc.va= 0x2000;  //bit13 is setvx = vc.a<< 18;return 0;}

 

这段代码之后vx的值为多少呢?

我想很多人都会给出vx=0x8000_0000  //bit31 is set 这个答案。这个答案在32位系统上是对的,但在64位系统上是错误的。

需要注意vx = vc.a<< 18 转换时,先转换成有符号的long long, 再转换成无符号的long long 类型。最后的结果是0xFFFF_FFFF_8000_0000

 

 

解决方式是

  •  
vx = (unsigned long long)vc.a<< 18;

 

      注意特殊数字      

如果长期在32位系统上编程,可能会形成一些思维惯性。对一些数值做一些假设。比如认为指针的大小就是4 byte,0xFFFFFFFF就是-1等。这些会影响到代码的移植性。

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#defineERROR_CODE 0xFFFFFFFFint bar(){ return -1;} int foo(){ if(ERROR_CODE==bar())}

 

      优化memory footprint      

引进64位带来很多performance的好处,但是也带来了一个缺点:

可能增加内存的占用,其中一个原因就是指针类型和地址由4byte变成了8byte,因此地址访问和指针占用内存更多。

有些测试数据,

Binary

ARMv7 Size (Bytes)

ARMv8 Size (Bytes)

Ratio

libcrypto.so

 1,052,920

 1,673,400

 1.59x

toolbox Android 5.1

 150,836

 255,280

 1.69x

这是所有64位构架系统的普遍现象。

 

那怎么可以帮助减少一些 footprint呢?

 

对于struct的对齐来说,如果没有packed等关键字修饰,struct的元素需要对齐到其自然边界,struct本身对齐到最大的元素大小。

因此以下数据结构的 memory layout为,

 

  •  
  •  
  •  
  •  
  •  
  •  
Struct{int a;int * p;int b;}

 

 

可以考虑做一些优化,调整元素在struct里面的位置,

 

  •  
  •  
  •  
  •  
  •  
  •  
Struct{int a;int b;int * p;}

 上文 你必须了解的64位编程知识(上篇)已经讲了些基本的知识,这篇文章开始通过一些例子来更好的呈现。

               指针运算              

64位编程中,可能会有很多指针运算编程需要注意的地方,比如下面处理,

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
short * test(short *ptr){ int n = (int) ptr+ 2;   ptr= (short *) n; return ptr;}

很多时候,因为没有考虑到之后的代码会在64位系统上运行,在指针运算时为了方便,可能会把指针强制类型转换成int类型。这在32位上是没问题的,因为指针在32位系统上和int类型大小都为4byte。但是在64位系统上,int类型是4byte,而指针类型是8 byte,如果做这样的强制类型转换,那么指针的高32 位会被截掉,从而产生错误。

上面的代码经编译,

 

  •  
  •  
ADD   w0, w0, #2   //32位的运算,因为写了W0,会将X0的高32bit 清零,也就是将指针的高32bit清零SXTW  x0, w0       //转换回64 bit,但高32位已经被清零

这是一个比较常见的问题,Google在将Android kernel升级支持64位时,很多的patch就是处理这样的问题,

https://android-review.googlesource.com/c/platform/frameworks/av/+/96922/1/media/libstagefright/codecs/on2/h264dec/omxdl/arm_neon/api/armCOMM.h

 

https://android-review.googlesource.com/c/platform/frameworks/base/+/35211/1/media/libstagefright/codecs/avc/enc/src/avcenc_api.cpp

这样的例子在Android里有很多,所以你如果也这样写过代码,no shame.

解决这个移植的问题的方式,上面patch已经给出:

如果需要对指针进行类型转换,使用intptr_t或uintptr_t类型,如果计算指针的差值,可以使用ptrdiff_t类型。这样写出来的代码可移植到32位和64位系统。

 

       指针运算的考虑       

下面一个例子,

 

  •  
  •  
  •  
  •  
  •  
  •  
int diff_a = -3;unsigned int diff_b =2 ;int * array = (int *)0x80004 ;int *p ; p= array+(a+b);

请问p的值是多少?请大家先花几秒时间想想。

 

 

 

如果你说是0x80003, 这大学C语言课程肯定没及格。

如果你说是0x80000, 在32位系统上是对的。

但是在64位系统上,你还得一步一步按照数据转换规则来,

 

         移位操作        

如果你需要对一个64bit的值的某些位设1,你也许会用这样的代码实现

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
long long val;long long shift(long long a, int bits){ a=a| (1<<bits); return a;} val=shift(val, 36);

 

以上代码是对64bit的val的36bit设1的操作。看出什么问题来了吗?

 

实际上,上面的代码并不正确。还记得前面说的如何对待数值常量吗?

  • 整型常量会被当成是可以表示这个值的最小类型,比如‘8’ 会是 int型

因此以上代码的1会被当成是int类型,对int类型移36位会出现什么样的结果呢?

实际上这个代码会生成如下指令,

 

  •  
  •  
  •  
  •  
MOV   w2, #1LSL    w1, w2, w1 //w1=bits=36SXTW  x1, w1   //将移位值32bit的w1有符号扩展成64bitORR   x0, x0, x1

根据LSL指令的描述,

http://shell-storm.org/armv8-a/ISA_v85A_A64_xml_00bet8/xhtml/lsl_ubfm.html 

 

  •  
  •  
  •  
  •  
  •  
  •  
LSL <Wd>, <Wn>, <Wm>is equivalent toLSLV <Wd>, <Wn>, <Wm> LSLVLogical Shift Left Variable shifts aregister value left by a variable number of bits, shifting in zeros, and writesthe result to the destination register. The remainder obtained by dividing thesecond source register by the data size defines the number of bits by which thefirst source register is left-shifted.

 

  •  
  •  
  •  
  •  
bits(datasize) result;bits(datasize) operand2 = X[m];result = ShiftReg(n,shift_type, UInt(operand2) MOD datasize);X[d] = result;

 

因此移位位数实际上 是(36 MOD 32),也就是左移4位,最后设的其实是bit4而不是bit36.

这个问题的解决方式就是指定‘1’这个常量为long long类型

 

  •  
  •  
  •  
  •  
  •  
long long shift(long long a, int bits){ a=a | (1LL<<bits); return a;}

 

这样生成的代码为,

 

  •  
  •  
  •  
MOV x2,#1LSLx1,x2,x1ORRx0,x0,x1

把1变成64位类型,因此LSL会移位36位,达到了目的。

一个实际driver开发中的例子,

https://patchwork.kernel.org/patch/4680781/ 

 

       位域操作      

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
Struct Test{unsignedshort va:15;unsigned shortvb:13;};Struct Testvc;unsignedlong long vx;int main(){vc.va= 0x2000;  //bit13 is setvx = vc.a<< 18;return 0;}

 

这段代码之后vx的值为多少呢?

我想很多人都会给出vx=0x8000_0000  //bit31 is set 这个答案。这个答案在32位系统上是对的,但在64位系统上是错误的。

需要注意vx = vc.a<< 18 转换时,先转换成有符号的long long, 再转换成无符号的long long 类型。最后的结果是0xFFFF_FFFF_8000_0000

 

 

解决方式是

  •  
vx = (unsigned long long)vc.a<< 18;

 

      注意特殊数字      

如果长期在32位系统上编程,可能会形成一些思维惯性。对一些数值做一些假设。比如认为指针的大小就是4 byte,0xFFFFFFFF就是-1等。这些会影响到代码的移植性。

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#defineERROR_CODE 0xFFFFFFFFint bar(){ return -1;} int foo(){ if(ERROR_CODE==bar())}

 

      优化memory footprint      

引进64位带来很多performance的好处,但是也带来了一个缺点:

可能增加内存的占用,其中一个原因就是指针类型和地址由4byte变成了8byte,因此地址访问和指针占用内存更多。

有些测试数据,

Binary

ARMv7 Size (Bytes)

ARMv8 Size (Bytes)

Ratio

libcrypto.so

 1,052,920

 1,673,400

 1.59x

toolbox Android 5.1

 150,836

 255,280

 1.69x

这是所有64位构架系统的普遍现象。

 

那怎么可以帮助减少一些 footprint呢?

 

对于struct的对齐来说,如果没有packed等关键字修饰,struct的元素需要对齐到其自然边界,struct本身对齐到最大的元素大小。

因此以下数据结构的 memory layout为,

 

  •  
  •  
  •  
  •  
  •  
  •  
Struct{int a;int * p;int b;}

 

 

可以考虑做一些优化,调整元素在struct里面的位置,

 

  •  
  •  
  •  
  •  
  •  
  •  
Struct{int a;int b;int * p;}

 

这个优化对程序中使用很多的数据结构有帮助,可以减少程序需要的内存。

 

         结语          

好了,本篇文章到此为止。文章的内容其实并不只针对 arm的系统,实际上是通用的内容。请记住,

  • 需要特别注意数据类型转换

  • 指针运算操作需要特别注意

  • 在不同数据类型之间运算需要注意

     

Enjoy 64bit system!

这个优化对程序中使用很多的数据结构有帮助,可以减少程序需要的内存。

 

         结语          

好了,本篇文章到此为止。文章的内容其实并不只针对 arm的系统,实际上是通用的内容。请记住,

  • 需要特别注意数据类型转换

  • 指针运算操作需要特别注意

  • 在不同数据类型之间运算需要注意

     

Enjoy 64bit system!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值