一、C语言常见陷阱
1. 数据模型
-
ARM64下可以采用LP64和ILP64数据模型,在Linux系统下默认采用LP64数据模型。
-
LP64中,L表示Long,P表示Pointer(指针长度),
ILP32、ILP64、LP64数据模型中不同数据类型的长度
数据类型/字节 | ILP32数据模型中的长度 | ILP64数据模型中的长度 | LP64数据模型中的长度 |
---|---|---|---|
char | 1 | 1 | 1 |
short | 2 | 2 | 2 |
int | 4 | 4 | 4 |
long | 4 | 4 | 8 |
long long | 8 | 8 | 8 |
pointer | 4 | 8 | 8 |
size_t | 4 | 8 | 8 |
float | 4 | 4 | 4 |
double | 8 | 8 | 8 |
2. 数据类型转换与整型提升
-
C语言隐式的数据类型转换:
-
在赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型
-
在算术表达式中,占字节少的数据类型向占字节多的数据类型转换。
-
在算术表达式中,当对有符号数据类型与无符号数据类型进行运算时,需要把有符号数据类型转化为无符号数据类型。
-
整数常量通常是int类型。例如,在ARM64系统里,整数8会使用Wn寄存器来存储,8LL则会使用Xn寄存器来存储
3. C语言规范中有一个整型提升的约定:
-
在表达式中,当使用有符号或者无符号的char,short,位域以及枚举类型时,都应该提升到int类型
-
如果上述类型可以使用int类型来表示,则使用int类型;否则使用unsigned int类型。
二、函数调用标准
三、栈分布
-
假设函数调用关系是main()->func1()->func2(),下图为栈的分布
2. ARM64体系结构的函数栈布局的关键点如下:
-
所有的函数调用栈都会组成一个单链表
-
每个栈由两个地址来构成这个链表,这两个地址都是64位宽的,并且它们都位于栈顶。
-
低地址存放:指向上一个栈 (父函数的栈)的栈基地址 FP,类似于链表的prev指针。本书把这个地址称为P_FP (Previous FP),以区别于处理器内部的FP寄存器。
-
高地址存放:当前函数的返回地址,也就是进入该函数时LR的值,本书把这个地址称为P_LR(Previous LR)。
3. 处理器的FP和SP寄存器相同。在函数执行时,FP和SP寄存器会指向该函数栈空间的FP处。
4. 函数返回时,ARM64处理器先把栈中的P_LR的值载入当前LR,然后执行RET指令。
四、系统调用
-
ARM64体系结构提供一个系统调用指令SVC,它允许应用程序通过SVC指令自陷到操作系统内核中,即陷入EL1中。
-
系统调用层主要有如下作用:
-
为用户空间中的程序提供硬件抽象接口。这能够让程序员从硬件设备底层编程中解放出来。例如,当需要读写文件时,程序员不用关心磁盘类型和介质,以及文件存储在磁盘哪个扇区等底层硬件信息。
-
保证系统稳定和安全。应用程序要访问内核就必须通过系统调用层,内核可以在系统调用层对应用程序的访问权限、用户类型和其他一些规则进行过滤,以避免应用程序不正确地访问内核。
-
可移植性。在不修改源代码的情况下,让应用程序在不同的操作系统或者拥有不同硬件体系结构的系统中重新编译并且运行。
3. 用户态调用SVC指令
-
操作系统为每个系统调用赋予了一个系统调用号,当应用程序执行系统调用时,操作系统通过系统调用号知道执行和调用哪个系统调用。