C语言相关结构信息

1  数组的分配和访问


对于构造类型的数据, 由于其包含多个基本类型数据,因而不能直接用单条指令来访问和运算,通常需要特定的 代码结构和寻址方式对其进行处理。
本节主要介绍构造类型和指针类型的数据在机器级程序中的访问和处理。
对于数组的访问和处理,编译器最重要的是要找到一种简便的数组元素地址的计算方法。


1.数组元素在存储空间的存放和访问


在程序中使用数组,必须遵循定义在前,使用在后的原则。一维数组定义的一般形式如下。
存储类型 数据类型 数组名[元素个数];
其中,存储类型可以缺省。

2.数组的存储分配和初始化


数组可以定义为静态存储型(static)、外部存型(extern)、 自动存储型(auto)或者定义为全局静态区数组,其 中,只有 auto 型数组被分配在栈中,其他存储型数组都分配在静态数据区。
数组的初始化就是在定义数组时给数组元素赋初值。.数组与指针
C 语言中指针与数组之间的关系十分密切,它们均用于处理存储器中连续存放的一组数据。因而在访问存储器时两 者的地址计算方法是统一的,数组元素的引用可以用指针来实现。
在指针变量的目标数据类型与数组元素的数据类型相同的前提条件下,指针变量可以指向数组或者数组中的任意 元素。结构体数据的分配和访问
C 语言的结构体(也称结构)可以将不同类型的数据结合在一个数据结构中。组成结构体的每个数据称为结构体的 成员或字段。


2.结构体成员在存储空间的存放和访问


结构体中的数据成员存放在存储器中一段连续的存储区中,指向结构的指针就是其第一个字节的地址。编译器在 处理结构型数据时,根据每个成员的数据类型获得相应的字节偏移量,然后通过每个成员的字节偏移量来访问结构成 员。

2.结构体数据作为入口参数

当结构体变量需要作为一个函数的形式参数时。形式参数和调用函数中的实参应该具有相同的结构。和普通变量 传递参数的方式一样。也有按值传递和按地址传递两种方式。
如果采用按值传递方式。则结构的每个成员都要被复制到栈中参数区。这既增加时间开销,又增加空间开销。因 而对于结构体变量通常采用按地址传递的方式。也就是说。对于结构类型参数。通常不会直接作为参数,而是把指向 结构的指针作为参数。这样,在执行 call 指令之前。就无须把结构成员复制到栈中的参数区,而只要把相应的结构体 首地址送到参数区,也即仅传递指向结构体的指针而不复制每个成员。

3  联合体数据的分配和访问


与结构体类似的还有一种联合体(简称联合)数据类型,它也是不同数据类型的集合。不过它与结构体数据相比, 在有储空间的使用方式上不同。结构体的每个成员占用各白的存储空间。而联合体的各个成员共享存储空间。也就是 说。在其一时刻,联合体的存储空间中仅在有一个成员数据。因此。联合体也称为共用体。
因为联合体的每个成员所占的存储空间大小可能不同。因而分配给它的存储空间总是按最大数据长度成员所需空 同大小为目标。
数据的对齐方式
可以把存储器看作由连续的位(cell)构成,每 8 位为一字节,每字节有一个地址编号,称为按字节编址。
假定计算机系统中规定主存存取单位为 64 位=8 字节,则第 0~7 字节同时读写,第 8~15 字节同时读写,以此类 推。
若一条指令要访问的数据不在地址为 8i~8i+7(i=0 ,1 ,2 , …) 之间的存储单元内,则需要多次访存,因而延长 了指令的执行时间。

4.struct结构体数据

对于由基本数据类型构造而成的 struct 结构体数据,为了保证其中每个字段都满足对齐要求,i386 System V ABI 对 struct 结构体数据的对齐方式有如下几条规则:
①整个结构体变量的对齐方式与其中对齐方式最严格的成员相同:
②每个成员在满足其对齐方式的前提下,取地址最小的可用位置作为成员在结构体
中的偏移量。这可能导致内部插空;
③结构体大小应为对齐边界长度的整数倍。这可能会导致尾部插空。

5.X86-64的基本特点

x86-64 的基本特点
随着计算机技术及应用领域的不断发展,32 位处理器逐步被 64 位处理器代替,最早的 64 位微处理器架构是 Intel 提出的采用全新指令集的 IA-64 ,而最早兼容 IA-32 的 64 位架构是 AMD 提出的 x86-64。
目前,AMD 的 64 位处理器架构 AMD64 和 Intel 的 64 位处理器架构 Intel 64 都支持 x86-64 指令集,因而,通常人 们直接使用 x86-64 代表 64 位 Intel 指令集架构。x86-64 有时也简称为 x64。
对于 Intel 架构机器中的编译器来说,可以有两种选择,一种是按 IA-32 指令集将目标编译成 IA-32 代码,一种是按 x86-64 指令集将目标编译成 x86-64 代码。
•在 IA-32 架构上运行的是 32 位操作系统,GCC 默认生成 IA-32 代码;
•在 x86-64 架构上运行的是 64 位操作系统,GCC 默认生成 x86-64 代码。
Linux 和 GCC 将前者称为“i386 ”平台,将后者称为“x86-64 ”平台。
与 IA-32 代码相比,x86-64 代码主要有以下几个方面的特点。
1)比 IA-32 具有更多的通用寄存器个数。新增的 8 个 64 位通用寄存器名称分别为 R8~R15 。它们可以作为 8 位寄 存器(R8B~R15B)、16 位寄存器(R8W~R15W)或 32 位寄存器(R8D~R15D)使用, 以访问其中的低 8 、低 16 或 低 32 位。
2) 比 IA-32 具有更长的通用寄存器位数,从 32 位扩展到 64 位。在 x86-64 中,所有通用寄存器都从 32 位扩充到了 64 位,名称也发生了变化。8 个 32 位通用寄存器 EAX 、EBX 、ECX 、EDX 、EBP 、ESP 、ESI 和 EDI 对应的 64 位寄存器 分别被命名为 RAX 、RBX 、RCX 、RDX 、RBP 、RSP 、RSI 和 RDI。
在 IA-32 中,寄存器 EBP 、ESP 、ESI 和 EDI 的低 8 位不能使用。而在 x86-64 架构中,可以使用这些寄存器的低 8 位, 对应寄存器名称为 BPL 、SPL 、SL 和 DL。
整数操作不仅支持 8 、16 、32 位数据类型,还支持 64 位数据类型。所有算术逻辑运算、寄存器与内存之间的数据 传输,都能以最多 64 位为单位进行操作。栈的压入和弹出操作都以 8 字节为单位进行。
3)字长从 32 位变为 64 位,因而逻辑地址从 32 位变为 64 位。指针(如 char*型)和长整数(long 型)数据从 32 位扩展到 64 位,与 IA-32 平台相比,理论上其数据访问的空间大小从 232B=4GB 扩展到了 264B=16EB。不过,目前仅支 持 48 位逻辑地址空间,即逻辑地址从 4GB 增加到了 256TB。
4)对于 long double 型数据,虽然还是采用与 IA-32 相同的 80 位扩展精度格式,但是,所分配的存储空间从 IA-32 的 12 字节大小扩展为 16 字节大小。也即,此类数据的边界从 4 字节对齐改为 16 字节对齐,不管是分配 12 字节还是 16 字节,都只会用到低 10 字节。
5)过程调用时,对于整型入口参数只有 6 个以内的情况,用通用寄存器而不是用栈来传递。因而,很多过程可以 不访问栈,使得大多数情况下执行时间比 IA-32 代码更短
6)128 位的 XMM 寄存器从原来的 8 个增加到 16 个,浮点操作采用基于 SSE 的面向 XMM 寄存器的指令集,浮点 数存放在 128 位的 XMM 寄存器中。x86-64 的过程调用
在 x86-64 中,过程调用通过寄存器传送参数,寄存器的使用约定主要包括以下方面①可以不用帧指针寄存器 RBP 作为栈帧底部,此时,使用 RSP 作为基址寄存器来访问栈帧中的信息,而 RBP 可作为普通寄存器使用
②传送入口参数的寄存器依次为 RDI 、RSI 、RDX 、RCX 、R8 和 R9 ,返回参数存放在 RAX 中;
③调用者保存的寄存器为 R10 和 R11 ,被调用者保存的寄存器为 RBX 、RBP 、R12 、R13 、R14 和 R15;
④RSP 用于指向栈顶元素;
⑤RIP 用于指向正在执行或即将执行的指令。
如果入口参数是整数类型或指针类型且少于等于 6 个,则无须用栈来传递参数,如果同时该过程无须在栈中存放 局部变量和被调用者保存寄存器的值,那么,该过程就不需要栈帧。传递参数时,如果参数是 32 位、16 位或 8 位,则 参数被置于对应宽度的寄存器部分。
若第一个入口参数是 char 型,则放在 RDI 中对应字节宽度的寄存器 DIL 中;若返回参数是 short 型,则放在 RAX 中对应 16 位宽度的寄存器 AX 中。
在 x86-64 中,最多可以有 6 个整型或指针型入口参数通过寄存器传递,超过 6 个入口参数时。后面的通过栈来传 递,在栈中传递的参数若是基本类型数据,则不管是什么基本类型都分配 8B 空间。当入口参数少于 6 个或者当入口参 数已经被用过而不再需要时,存放对应参数的寄存器可以被函数作为临时寄存器使用。
对于存放返回结果的 RAX 寄存器,在产生最终结果之前。也可以作为临时寄存器被函数重复使用。
在 x86-64 中,调用指令 call 将一个 64 位返回地址保存在栈中并执行 R[rsp]←R[rsp]-8。
返回指令 ret 也是从栈中取出 64 位返回地址并执行 R[rsp] ←R[rsp]+8。
x86-64 的基本指令和对齐
x86-64 指令集在兼容 IA-32 的基础上支持 64 位数据操作指令,大部分操作数指示符与 IA-32 一样,所不同的是, 当指令中的操作数为存储器操作数时,其基址寄存器或变址寄存器都必须是 64 位寄存器;此外,在运算类指令中,除 了支持原来 IA-32 中的寻址方式以外,x86-64 还支持 PC 相对寻址方式。

1.数据传送指令


在 x86-64 中,提供了一些在 IA-32 中没有的数据传送指令,例如,
movabsq 指令用于将一个 64 位立即数送到一个 64 位通用寄存器中;
movq 指令用于传送一个 64 位的四字;
movsbq 、movswq 、movslq 用于将源操作数进行符号扩展并传送到一个 64 位寄存器中;
movzbq 、movzwq 用于将源操作数进行零扩展后传送到一个 64 位寄存器中;
leaq 用于将有效地址加载到 64 位寄存器;
pushg 和 popq 分别是四字压栈和四字出栈指令。
汇编指令中指令助记符结尾处的“q ”表示操作数长度为四字(64 位)。
在 x86-64 中,movl 指令的功能与在 IA-32 中不同,它在传送 32 位寄存器内容的同时。
还会将目的寄存器的高 32 位自动清 0 ,因此,在 x86-64 中,movl 指令的功能相当于 movzlq 指令,因而在 x86-64 中不需要 movzlq 指令。
2.算术逻辑运算指令
在 x86-64 中,增加了操作数长度为四字的运算类指令(长度后缀为 q),例如,addq(四字相加)、subq(四字 相减)、imulq(带符号整数四字相乘)、mulq(无符号整数四字相乘)、orq(64 位相或)、incq(增 1)、decq(减 1)、negq(取负)、notq(各位取反)、salq(算术左移)等。
例 3.13   以下是 C 语言赋值语句“x=a*b+c*d;”对应的 x86-64 汇编代码,已知 x 、a 、b 、c 和 d 分别在寄存器 RAX、
RDI 、RSI 、RDX 和 RCX 对应宽度的寄存器中。根据以下汇编代码,推测变量 x 、a 、b 、c 和 d 的数据类型。
1 movslql          %ecx ,%rcx
2 imulq             %rdx ,%rcx
3 movsbl             %sil ,%esi
4 imull               %edi ,%esi
5 movslq                    %esi ,%rsi
6 leaq                (%rcx ,%rsi),%rax3.数据对齐
与 IA-32 一样,x86-64 中各种类型数据应该遵循一定的对齐规则,而且要求更加严格。因为 x86-64 中存储器的访 问接口被设计成按 8 字节或 16 字节为单位进行存取,其对齐规则是,任何 K 字节宽的基本数据类型和指针类型数据的 起始地址一定是 K 的倍数。
因此,long 型、double 型数据和指针型变量都必须按 8 字节边界对齐;longdouble 型数据必须按 16 字节边界对齐。 具体的对齐规则可以参考 AMD64 System VABI 手册。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值