- 论坛徽章:
- 3
|
果断要从调用惯例/约定说起. 这个约定通常是ABI的一部分.
调用惯例/约定规定了, 当一个函数(caller)调用其他函数(被调用函数, callee)时, callee不应当修改/覆盖caller调用callee后要用到的哪些寄存器.
即:- void callee(void)
- {
- ...
- }
- void caller(void)
- {
- // [1] ...
- callee();
- // [2] ...
- }
复制代码 ABI的调用惯例/约定保证, 在caller调用callee之前(即[1])处, 与caller调用callee之后(即[2])处, 哪一些寄存器的内容是确保不会改变的.比如x86, 一般规定ebx, esi, edi都是调用callee前后不会改变的 -- 亦即, 如果callee修改了ebx, or esi, edi, 那么callee在修改之前要先将其保存到栈上, 然后修改, 修改完返回到调用方之前要恢复ebx, or esi, edi.
那么, 言外之意也就是说, 还有一部分寄存器的内容是会被改变的. 比如x86, eax, ecx, edx都是调用惯例不保证的. 既然调用惯例不保证caller调用callee前后其值不变, 那么caller在调用callee之前, 应当自己先把寄存器eax, ecx, edx都保存到栈上, 等调用完毕, 又自己恢复它们的值. 那么, eax, ecx, edx即被称为 call-clobbered -- 调用函数后不保证其内容和调用前不变. 即是有可能被破坏的意思.
请注意 -- 这个调用惯例/约定是规定编译器该怎么做.即, 根据这个约定, 编译器知道caller调用前应该保存哪些寄存器, 调用后恢复哪些寄存器, 同样, 编译器也知道callee应该在对哪些寄存器进行修改前要先保存, 返回前要先恢复.
回到LZ的例子. 前面一段大意是, 嵌入式汇编中, 因为寄存器多是gcc自动分配, 没有某种强制的约束字, 于是当你想自己强制约束寄存器和变量的对应时怎样怎样云云.- register int *p1 asm("r0") = ...; // op1
- register int *p2 asm("r1") = ...; // op2
- register int *result asm("r0");
复制代码 这段代码中, op1是无所谓的. 就算op1调用了函数, 这个函数修改了某些寄存器的值, 但是编译器和ABI会保证, 你调用前后寄存器的值都是一致的 -- 反正不是调用方 保存/恢复 就是被调用方 保存/恢复.
但是op2就不同了. 原因就是写代码的人已经擅自强制使用了寄存器(例子中是r0). C语言和编译器 -- 没办法/或者很难? 约束/预知 Coder的这个行为. 因为Coder擅自使用了寄存器, 那么责任就应该由Coder自己负责, 编译器不知道应该调用前保存哪个, 调用后恢复哪个. 即, 如果op2是一个函数调用, 那么显然op2中会破坏一些寄存器, 本来编译器根据ABI是知道哪些应该恢复的, 但是Coder又使用了, 所以, 这个责任Coder自己负责, 就是这个意思(ps, 一般的C语言的赋值操作怎么可能调函数, 不是mov指令就完事的么. C++重载赋值操作符了才得那么干. 当然C语言的结构体变量赋值倒也不是一个mov万事那么简单, memcpy也就行了. 那个op2是指代 '...', 即starwing83解释的那样, op2调函数了).
基本上, 我认为starwing83的解释也是对的. 我只是先啰嗦了调用约定(r0, r1神马的果断是ARM吗. 那么就是APCS了).
==========================================
至于第二个, 我认为也应该从调用约定的出发点去理解. 基本上, call-Clobber list中的寄存器就是不适宜使用的, 否则Coder自己care.
==========================================
呃... 当然我可能根本没理解或者解释错了. 那么LZ砖轻拍.
|
|