欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-08 08:44:56
...

不管你乐不乐意,软件程序员包括嵌入式软件程序员的世界已经快速向64位进化。你必须了解的64位编程知识(上篇)

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

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

2017年Apple宣布从iOS11开始所有的应用都需要是64bit的,

https://developer.apple.com/documentation/uikit/app_and_environment/updating_your_app_from_32-bit_to_64-bit_architecture 

iOS 11and later, all apps use the 64-bit architecture. If your app targets an earlierversion of iOS, you must update it to run on later versions.

Google宣布从2019年8月开始, 所有在 google store 上的应用必须有64bit的版本。

https://android-developers.googleblog.com/2019/01/get-your-apps-ready-for-64-bit.htm

Starting August 1, 2019: 

All new apps andapp updates that include native code are required to provide 64-bit versions inaddition to 32-bit versions when publishing to Google Play.

Starting August 1, 2021:

Google Play willstop serving apps without 64-bit versions on 64-bit capable devices, meaningthey will no longer be available in the Play Store on those devices. 

自2011年arm公布armv8-a 64构架以来,到现在已经近10年。Arm的软件生态系统快速向64位进化,Linux kernel arm构架支持的演化基本只在arm64上实施。

Arm从Cortex-A76开始只在EL0提供32位(aarch32)的支持,在EL1-EL3 CPU硬件只提供64位支持(aarch64),这包括Cortex-A77,A78处理器。在不久的将来,arm提供的应用处理器在EL0-EL3都只支持64位。虽然Cortex-A53/A57/A72/A73/A55/A75这些处理器还是在EL0-EL3都支持32和64位。

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

而且arm后面推出的v8-R64位构架(第一个CPU为Cortex-R82)只支持64位。

 

早在2011年arm刚推出64bit构架时,很多工程师没有太多接触到64位构架的处理器,因此对在64位处理器上编程有些困惑。因此,当年我特地写了一个有关如何从arm 32 bit迁移到64bit的培训内容提供给给客户。并且与Chris合作写了一个白皮书,

https://community.arm.com/cfs-file/__key/telligent-evolution-components-attachments/01-2142-00-00-00-00-52-01/Porting-to-ARM-64_2D00_bit.pdf

 

随着Cortex-A,Cortex-R处理器的演化,会有越来越多的人在64位系统上开发固件,driver, 应用。因此希望此文可以有些帮助。

 

本文主要讲述C/C++编程相关的考虑,汇编相关的考虑不在此文讨论范围。

 

                   概述                        

好的方面是,Arm的64位编程需要注意的事项和其他64位构架基本一样。

大多数的工作都是由使用aarch64的编译工具完成,只要将代码重新编译即可,但是有一些注意事项:

  1. 有些代码在32位系统上可以正常工作,但移植到64位系统上可能会有问题

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

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

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

 

                     数据模型                    

在32位系统和64位系统上数据类型的大小有所不同,

ILP32是32位系统上使用的数据类型,LLP64是指64位系统使用一种数据模型,long类型数据大小还是4byte,而longlong类型的数据大小是8byte,这种数据模型在Windows上使用,而Linux系统上采用的是LP64数据模型,long类型大小是8byte, long long 类型也是8 byte. 而int类型在32位和64位系统上,都是4 byte。

对于指针类型,在32位系统上,其类型大小为4 byte,在64位系统上为8 byte。

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

当然如果你之前使用的16位,8位CPU系统,那么int类型可能相应的是2byte和1 byte, 那么移植这样的代码到32位系统,以下内容也有帮忙。

 

     重回大学,C语言知识    

C语言博大精深,由于对CPU构架有了更好的认识,越来越理解C语言设计后面的原理和考虑。

如果你精通C语言,下面的章节可以忽略。但是很多人因为会忽略C语言定义的基本规则,从而导致代码移植的问题。当然这也是正常的,毕竟很多人之前很长时间里只写过在32位CPU上运行的代码,而且代码跑的很好。

 

   C语言的通用类型转换规则   

这些规则包含:

  1. 显式数据类型转换

         Type casting, 比如  (int) a;

         强制数据类型转换

    2. 隐式数据类型转换

  1. Integral promotion

  2. Usual 算式转换

这些数据类型转换对写出可移植的代码非常关键。

 

   Integral promotion  

著名的K&R C编程语言里面说,

“A character, a short integer, or aninteger bit-field, signed or unsigned, or an object of enumeration type, may beused in an expression wherever an integer maybe used.

“If an int type can represent all values ofthe original type, the value is converted to an int, otherwise it is convertedto an unsigned int.”

一个使用有符号或是无符号char, short, 整型位域类型和枚举类型的表达式,使用int整型来表达。

如果一个有符号int类型可以表达原来类型的所有值的话,原来的数据类型转换成有符号int类型,否则的话转换乘无符号int类型。

 

也就是说,如果一个整型类型(char, short)的大小小于int类型大小,那么在运算过程中,需要先被隐式转换成int或是unsigned int类型。当然其在存储在内存里面的大小还是原始大小。这个结合到armv8构架也好理解:

Arm构架是个load/store的RISC构架,对一个数据运算时,需要从内存中取出这个值到寄存器中,然后做ALU运算,最后写回到内存。

而做ALU运算时,运算的寄存器是整个W(32bit)或是X(64bit)寄存器中进行,而不会只做8bit,16bit的运算。这样的话,需要将char,short类型进行符号位/零扩展到32bit W寄存器。一个例子,要进行以下操作

  •  
  •  
char a;a+=1;

实际对应到汇编代码为,

  •  
  •  
  •  
LDRSB W0, [X1]    //从&a的内存中取出1byte,并将bit7符号位扩展到整个32 bit W0ADD W0, W0, #1[  //32bit加法,+1STRB W0, [X1]    //将1byte,写回内存

 

这个Integral promotion可以由下表表示

 

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

举个例子

  •  
  •  
  •  
  •  
  •  
unsigned char va= 0x55;if (~va == 0xAA)   return 1;else   return 0;

 

上面是return 1还是0呢?

也许你的算法是 ~va= ~(0x55) = 0xAA

其实这是有问题的,因为~va是个表达式,根据上面的讲述,char需要做Integral promotion,因此~va的值在32位系统上是0xFFFFFFAA, 所以返回的是0.

 

   Usual 算式转换   

大学里面学过,如果两个类型不一样的数据进行运算,需要把小的数据类型先转换成大的数据类型,再进行运算。比如

  •  
  •  
  •  
int a;longlong b, c;c=a+b;

先要把a转换成long long 类型再进行加法运行。结合到arm的指令,虽然有 W和X寄存器,他们可以在代码里混用,

  •  
  •  
ADD W2, W1, W0ADD X5, X4, X3

但是没有

  •  
ADD X3, X2, W1

对于 char/short 类型,先进行Integral promotion,然后再转换类型。

Usual 算式转换包含

  • Arithmetic operators with two operands:*,/,%,+, and-

  • Relational and equality operators:<,<=,>,>=,==, and!=

  • The bitwise operators,&,|, and^

  • The conditional operator,?:(for the second and third operands)

 

这里隐式转换的规则为

 

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

 

有时候,符号位和类型大小都需要隐式转换

int+ long -> long

unsigned + signed -> unsigned

这时候转换的顺序是

当一个负的有符号整型提升为无符号的相同或更大类型的整型时,它首先提升为更大类型的有符号整型,然后再转换成无符号类型,比如,

  •  
  •  
  •  
  •  
inta = -1;unsigned long long b = 2;unsigned long long c;c = a + b;

 

  1. 先转化成signed long long  (0xFFFF_FFFF_FFFF_FFFF), -1LL

  2. 再转化成 unsigned long long,  (0xFFFF_FFFF_FFFF_FFFF)

 

另外在使用一个数字常量时

  • 整型常量(除非有后缀,比如8LL)会被当成是可以表示这个值的最小类型,比如8 会是 int型

  • 如果这个常数是16进制的,编译器可以当成有符号或是无符号数来处理

  • 十进制的常数总会被当成有符号数来处理

 

下篇内容通过一些实际的例子来分析64位编程需要注意的事项。

相关标签: arm