Writing portable code regarding the processor architecture/zh CN

From Free Pascal wiki
Jump to navigationJump to search

English (en) Bahasa Indonesia (id) русский (ru) 中文(中国大陆) (zh_CN)

编写处理器架构无关的可移植代码

在编写处理器架构无关的可移植代码时,主要问题有:字节序、32 位或 64 bit 位处理器。

字节序

字节序是指处理器如何存储超过单个字节的数据(比如 16/32/64 位整数)。

Free Pascal 支持两种字节序的处理器:

  1. 低位存于低地址;longint(4) 编码为 04 00 00 00 (小端序
  2. 高位存于地地址;longint(4) 编码为 00 00 00 04 (大端序

旁注:

中端序(也称混合序)处理器目前已很少见了。最有名的中端序处理器是 DEC 的 PDP-11,但已作古。

每种系列的处理器通常会固定采用一种字节序,但有些系列处理器能根据主板的不同而采用大端或小端序(如ARM、PPC)。

最有名的小端序处理器系列就是用于 PC 的 x86,以及其兄弟 x86-64。典型的大端序处理器有 PPC(通常情况下,参阅上注)、m68k、诸如 HP3000 之类的小型机、诸如 IBM 370(Z 系列)之类的大型机。

因为 TCP/IP 规定了所有网络协议头部结构都应该是大端序,所以有时大端序也被称做“网络序”。

在以下情况下,字节序十分的重要:

  1. 在不同架构的处理器之间交换数据
  2. 访问同一块数据,有时视作较大类型(如整数)的数组,有时又要视作字节数组

以下是后一种情况的示例

type
  Q = record
    case Boolean of
      True: (i: Integer);
      False: (p: array[1..4] of Byte);
  end;
 
var 
  x:^Q;
 
begin
  // 首先显示一下编译器识别出来的字节序
  {$IFDEF ENDIAN_LITTLE}
  Writeln('The compiler has compiled this program for Little Endian machines (such as Intel x86, ARMEL)');
  {$ENDIF}
  {$IFDEF ENDIAN_BIG}
  Writeln('The compiler has compiled this program for Big Endian machines (such as PowerPC, ARMEB)');
  {$ENDIF}  
  New(x);
  x^.i := 5;
  if x^.p[1] = 5 then
    WriteLn(x^.p[1],' Your machine is Little Endian')
  else
  if x^.p[4] = 5 then
    WriteLn(x^.p[1],' Your machine is Big Endian')
  else
    WriteLn(x^.p[1],' ',x^.p[2],' ',x^.p[3],' ',x^.p[4],' Your machine''s endianness is indeterminate; please report the results to the compiler development team');  
  WriteLn;
 
  { 暂停一下,便于用户查看结果 }
  Write('Press Enter when you finish reading this');
  ReadLn;
end.

在小端序机器(PC)中,上述代码将会输出 '5'(因为 longint(5) 在内存中存为 05 00 00 00),而在大端序机器上(如 Powermac)则会显示 '0'(因为 longint(5) 在内存中存为 00 00 00 05)。(如果显示 indeterminate,请在本 wiki 页报告,并注明处理器型号和显示的内容!)

若要检测处理器的字节序,请使用 ENDIAN_BIG 或 ENDIAN_LITTLE(自 1.9 版起还可用 FPC_LITTLE_ENDIAN 和 FPC_BIG_ENDIAN),Free Pascal 会自动根据处理器进行定义。

改变字节序

system 单元中包含了将指定字节序转换为本机(CPU)字节序的函数,大端序(BEtoNNtoBE),小端序为(LEtoNNtoLE),还有 SwapEndian

很多网络库(如 Synapse)都提供了自己的转换函数,以供在网络和主机之间转换字节序。

内存对齐

有些处理器允许处理未对齐的数据,只是会降低性能(IBM 370/Z 系列)。而有些处理器在数据未对齐时则会触发硬件异常(如 Alpha 和 ARM)。有时操作系统会捕获这类硬件异常并通过模拟器进行修复,但处理过程会非常缓慢,应该尽量避免。未对齐的数据还会导致记录大小不一致,因此一定要用 sizeof(记录类型); 作为记录的大小。如果定义的是 packed record,请尽量确保数据的自然对齐。有些处理器(如老款 PowerPC)只会对特定的数据类型有对齐的要求,比如浮点数。

若要知道 CPU 是否要求数据对齐,可以检测 FPC_REQUIRES_PROPER_ALIGNMENT(1.9 及以上版本)。在32位 CPU 中,通常意味着小于 4 字节的数据必须自然对齐。 如果要访问未对齐的数据,请在处理之前用 move 将其转移至对齐的内存区域。move 函数会检查未对齐数据并进行妥善处理。

对齐有以下多种策略:

  • 每个字段都对齐至某值的倍数(通常是2的幂,1、2、4、8。1 等同于 packed)
  • 在每个字段前填充值,确保按自身大小的倍数对齐(longint 按4字节对齐,int64 按8字节对齐,依此类推)。通常这会由 C 编译器完成,因此 FPC 称为 {$packrecords C}。

(对于 数组 或是 嵌套记录,会按最大子项对齐)

macOS 中的 {$packrecords C} 貌似在末尾填充了整条记录,使其达到一定大小。此问题还在调查中,以后可能会在编译器中修复。

32 位 和 64 位

为了最大程度保持与老旧代码的兼容性,在从 32 位转为 64 位时,FPC 不会更改预定义数据类型的大小(如 integerlongintword)。但在 64 位架构中,指针的大小为 8 个字节,因此像 longint(pointer(p)) 这种代码必定会崩溃。但为了支持可移植代码的编写,FPC 的 system 单元引入了 PtrIntPtrUInt 类型,表示有符号和无符号整数,大小与指针相同。

请记住,修改 "pointer" 类型的大小也会影响其指向的记录大小。如果记录大小是固定的,而不是通过 newgetmem (<x>,sizeof(<x>)) 分配内存的,那就必须对修改指针的代码进行修正。

上述规则与大多数开放 Unix 平台一致。不过在商用领域有一些例外,比如 Tru64 和 ILP64。

函数调用约定

一般而言:请勿依赖内部代码,例如 const 参数的传入是通过栈还是直接传值。

修饰符 顺序 栈的清理者 对齐方式 寄存器值保存吗?
none 从左到右 函数 默认值
Register 从左到右 函数 默认值
CDecl 从右到左 调用方 GCC 对齐 GCC 寄存器
Interrupt 从右到左 函数 默认值 全部寄存器
Pascal 从左到右 函数 默认值
SafeCall 从右到左 函数 默认值 GCC 寄存器
StdCall 从右到左 函数 GCC 对齐 GCC 寄存器
OldFPCCall 从右到左 调用方 默认值

详细信息和其他约定,请参阅 $CALLING

各种处理器架构下的大小限制

处理器架构 参数 本地变量
i386 64 KiB 没有限制
AMD64/x86-64 64 KiB 没有限制
Motorola 68000 32 KiB 32 kiB
Motorola 68020 32 KiB 没有限制
PPC 没有限制 没有限制
ARM 没有限制 没有限制
SPARC 没有限制 没有限制

大型机

关于 IBM 370 Z 系列,更多内容请参阅 ZSeries

x86

在 x86 处理器上,通常所有参数都通过堆栈传递。但在 Free Pascal 中,首选采用 FastCall 或 Register 约定(DarwinmacOS 除外)。与 Delphi 兼容的 Register 约定规定了前三个值或单地址参数通过寄存器 EAX、EDX 和 ECX 传递。

ARM

PPC

由于 PowerPC 架构拥有大量寄存器,因此大多数函数的单级调用可用寄存器传递所有参数。必要时会用栈传递其他参数。如果是多级调用且必须保存寄存器值,则一定会在栈上为基于寄存器的参数额外分配空间。

PPC 处理器采用标准的 AIX 或 SysV 调用约定。详情请参阅 PPC Calling conventions

68K

68K 处理器支持 CDecl 和 Pascal 约定。68K 栈帧模型是具有帧指针(FP)和堆栈指针(SP)的传统向下增长栈。68K 硬件对栈中的参数强制执行 16 位对齐。详情请参阅68K 与 PowerPC 的对比

大多数 Macintosh Toolbox 函数(经典 Mac OS 的高层函数)都推荐 Pascal 式的调用,尽管一些较新的管理器遵循 C 约定。Macintosh 操作系统函数(经典 Mac OS 的底层函数)会将调用结果放入寄存器中,也从寄存器中读取结果。

Pascal 函数刚被调用时,返回地址位于栈顶。随后函数会用 LINK 指令创建一个栈帧,并将临时寄存器中的值保存其中,因此在函数刚开始执行时的栈将如下所示:

返回值
第一个实参
...
最后一个实参
指向 opt 的静态链接
返回地址
A6 前一个 A6 寄存器
本地变量
SP 保存的寄存器值

参考


navigation bar: data types
simple data types

boolean byte cardinal char currency double dword extended int8 int16 int32 int64 integer longint real shortint single smallint pointer qword word

complex data types

array class object record set string shortstring




参阅

  • [[Multiplatform Programming Guide]|跨平台编程指南]