当前位置: 首页 > news >正文

做音乐网站用什么程序烟台电子商务网站建设

做音乐网站用什么程序,烟台电子商务网站建设,太原建网站,wordpress seo技巧From#xff1a;https://www.cnblogs.com/clover-toeic/p/3755401.html https://www.cnblogs.com/clover-toeic/p/3756668.html 程序的执行过程可看作连续的函数调用。当一个函数执行完毕时#xff0c;程序要回到调用指令的下一条指令(紧接call指令)处继续执行。函数调用过…  Fromhttps://www.cnblogs.com/clover-toeic/p/3755401.html    https://www.cnblogs.com/clover-toeic/p/3756668.html 程序的执行过程可看作连续的函数调用。当一个函数执行完毕时程序要回到调用指令的下一条指令(紧接call指令)处继续执行。函数调用过程通常使用堆栈实现每个用户态进程对应一个调用栈结构(call stack)。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量。 不同处理器和编译器的堆栈布局、函数调用方法都可能不同但堆栈的基本概念是一样的。 1 寄存器分配 寄存器是处理器加工数据或运行程序的重要载体用于存放程序执行中用到的数据和指令。因此函数调用栈的实现与处理器寄存器组密切相关。 Intel 32位体系结构(简称IA32)处理器包含8个四字节寄存器如下图所示 最初的8086中寄存器是16位每个都有特殊用途寄存器名城反映其不同用途。由于IA32平台采用平面寻址模式对特殊寄存器的需求大大降低但由于历史原因这些寄存器名称被保留下来。在大多数情况下上图所示的前6个寄存器均可作为通用寄存器使用。某些指令可能以固定的寄存器作为源寄存器或目的寄存器如一些特殊的算术操作指令imull/mull/cltd/idivl/divl要求一个参数必须在%eax中其运算结果存放在%edx(higher 32-bit)和%eax (lower32-bit)中又如函数返回值通常保存在%eax中等等。为避免兼容性问题ABI规范对这组通用寄存器的具体作用加以定义(如图中所示)。 对于寄存器%eax、%ebx、%ecx和%edx各自可作为两个独立的16位寄存器使用而低16位寄存器还可继续分为两个独立的8位寄存器使用。编译器会根据操作数大小选择合适的寄存器来生成汇编代码。在汇编语言层面这组通用寄存器以%e(ATT语法)或直接以e(Intel语法)开头来引用例如mov $5, %eax或mov eax, 5表示将立即数5赋值给寄存器%eax。 在x86处理器中EIP(Instruction Pointer)是指令寄存器指向处理器下条等待执行的指令地址(代码段内的偏移量)每次执行完相应汇编指令EIP值就会增加。ESP(Stack Pointer)是堆栈指针寄存器存放执行函数对应栈帧的栈顶地址(也是系统栈的顶部)且始终指向栈顶EBP(Base Pointer)是栈帧基址指针寄存器存放执行函数对应栈帧的栈底地址用于C运行库访问栈中的局部变量和参数。 注意EIP是个特殊寄存器不能像访问通用寄存器那样访问它即找不到可用来寻址EIP并对其进行读写的操作码(OpCode)。EIP可被jmp、call和ret等指令隐含地改变(事实上它一直都在改变)。 不同架构的CPU寄存器名称被添加不同前缀以指示寄存器的大小。例如x86架构用字母“e(extended)”作名称前缀指示寄存器大小为32位x86_64架构用字母“r”作名称前缀指示各寄存器大小为64位。 编译器在将C程序编译成汇编程序时应遵循ABI所规定的寄存器功能定义。同样地编写汇编程序时也应遵循否则所编写的汇编程序可能无法与C程序协同工作。 【扩展阅读】栈帧指针寄存器 为了访问函数局部变量必须能定位每个变量。局部变量相对于堆栈指针ESP的位置在进入函数时就已确定理论上变量可用ESP加偏移量来引用但ESP会在函数执行期随变量的压栈和出栈而变动。尽管某些情况下编译器能跟踪栈中的变量操作以修正偏移量但要引入可观的管理开销。而且在有些机器上(如Intel处理器)用ESP加偏移量来访问一个变量需要多条指令才能实现。 因此许多编译器使用帧指针寄存器FP(Frame Pointer)记录栈帧基地址。局部变量和函数参数都可通过帧指针引用因为它们到FP的距离不会受到压栈和出栈操作的影响。有些资料将帧指针称作局部基指针(LB-local base pointer)。 在Intel CPU中寄存器BP(EBP)用作帧指针。在Motorola CPU中除A7(堆栈指针SP)外的任何地址寄存器都可用作FP。当堆栈向下(低地址)增长时以FP地址为基准函数参数的偏移量是正值而局部变量的偏移量是负值。 2 寄存器使用约定 程序寄存器组是唯一能被所有函数共享的资源。虽然某一时刻只有一个函数在执行但需保证当某个函数调用其他函数时被调函数不会修改或覆盖主调函数稍后会使用到的寄存器值。因此IA32采用一套统一的寄存器使用约定所有函数(包括库函数)调用都必须遵守该约定。 根据惯例寄存器%eax、%edx和%ecx为主调函数保存寄存器(caller-saved registers)当函数调用时若主调函数希望保持这些寄存器的值则必须在调用前显式地将其保存在栈中被调函数可以覆盖这些寄存器而不会破坏主调函数所需的数据。寄存器%ebx、%esi和%edi为被调函数保存寄存器(callee-saved registers)即被调函数在覆盖这些寄存器的值时必须先将寄存器原值压入栈中保存起来并在函数返回前从栈中恢复其原值因为主调函数可能也在使用这些寄存器。此外被调函数必须保持寄存器%ebp和%esp并在函数返回后将其恢复到调用前的值亦即必须恢复主调函数的栈帧。 当然这些工作都由编译器在幕后进行。不过在编写汇编程序时应注意遵守上述惯例。 3 栈帧结构 函数调用经常是嵌套的在同一时刻堆栈中会有多个函数的信息。每个未完成运行的函数占用一个独立的连续区域称作栈帧(Stack Frame)。栈帧是堆栈的逻辑片段当调用函数时逻辑栈帧被压入堆栈, 当函数返回时逻辑栈帧被从堆栈中弹出。栈帧存放着函数参数局部变量及恢复前一栈帧所需要的数据等。 编译器利用栈帧使得函数参数和函数中局部变量的分配与释放对程序员透明。编译器将控制权移交函数本身之前插入特定代码将函数参数压入栈帧中并分配足够的内存空间用于存放函数中的局部变量。使用栈帧的一个好处是使得递归变为可能因为对函数的每次递归调用都会分配给该函数一个新的栈帧这样就巧妙地隔离当前调用与上次调用。 栈帧的边界由栈帧基地址指针EBP和堆栈指针ESP界定(指针存放在相应寄存器中)。EBP指向当前栈帧底部(高地址)在当前栈帧内位置固定ESP指向当前栈帧顶部(低地址)当程序执行时ESP会随着数据的入栈和出栈而移动。因此函数中对大部分数据的访问都基于EBP进行。 为更具描述性以下称EBP为帧基指针 ESP为栈顶指针并在引用汇编代码时分别记为%ebp和%esp。 函数调用栈的典型内存布局如下图所示 图中给出主调函数(caller)和被调函数(callee)的栈帧布局m(%ebp)表示以EBP为基地址、偏移量为m字节的内存空间(中的内容)。该图基于两个假设第一函数返回值不是结构体或联合体否则第一个参数将位于12(%ebp) 处第二每个参数都是4字节大小(栈的粒度为4字节)。在本文后续章节将就参数的传递和大小问题做进一步的探讨。  此外函数可以没有参数和局部变量故图中“Argument(参数)”和“Local Variable(局部变量)”不是函数栈帧结构的必需部分。 从图中可以看出函数调用时入栈顺序为 其中主调函数将参数按照调用约定依次入栈(图中为从右到左)然后将指令指针EIP入栈以保存主调函数的返回地址(下一条待执行指令的地址)。进入被调函数时被调函数将主调函数的帧基指针EBP入栈并将主调函数的栈顶指针ESP值赋给被调函数的EBP(作为被调函数的栈底)接着改变ESP值来为函数局部变量预留空间。此时被调函数帧基指针指向被调函数的栈底。以该地址为基准向上(栈底方向)可获取主调函数的返回地址、参数值向下(栈顶方向)能获取被调函数的局部变量值而该地址处又存放着上一层主调函数的帧基指针值。本级调用结束后将EBP指针值赋给ESP使ESP再次指向被调函数栈底以释放局部变量再将已压栈的主调函数帧基指针弹出到EBP并弹出返回地址到EIP。ESP继续上移越过参数最终回到函数调用前的状态即恢复原来主调函数的栈帧。如此递归便形成函数调用栈。 EBP指针在当前函数运行过程中(未调用其他函数时)保持不变。在函数调用前ESP指针指向栈顶地址也是栈底地址。在函数完成现场保护之类的初始化工作后ESP会始终指向当前函数栈帧的栈顶此时若当前函数又调用另一个函数则会将此时的EBP视为旧EBP压栈而与新调用函数有关的内容会从当前ESP所指向位置开始压栈。 若需在函数中保存被调函数保存寄存器(如ESI、EDI)则编译器在保存EBP值时进行保存或延迟保存直到局部变量空间被分配。在栈帧中并未为被调函数保存寄存器的空间指定标准的存储位置。包含寄存器和临时变量的函数调用栈布局可能如下图所示 在多线程(任务)环境栈顶指针指向的存储器区域就是当前使用的堆栈。切换线程的一个重要工作就是将栈顶指针设为当前线程的堆栈栈顶地址。 以下代码用于函数栈布局示例 //StackFrame.c #include stdio.h #include string.hstruct Strt{int member1;int member2;int member3; };#define PRINT_ADDR(x) printf(#x %p\n, x) int StackFrameContent(int para1, int para2, int para3){int locVar1 1;int locVar2 2;int locVar3 3;int arr[] {0x11,0x22,0x33};struct Strt tStrt {0};PRINT_ADDR(para1); //若para1为char或short型则打印para1所对应的栈上整型临时变量地址PRINT_ADDR(para2);PRINT_ADDR(para3);PRINT_ADDR(locVar1);PRINT_ADDR(locVar2);PRINT_ADDR(locVar3);PRINT_ADDR(arr);PRINT_ADDR(arr[0]);PRINT_ADDR(arr[1]);PRINT_ADDR(arr[2]);PRINT_ADDR(tStrt);PRINT_ADDR(tStrt.member1);PRINT_ADDR(tStrt.member2);PRINT_ADDR(tStrt.member3);return 0; }int main(void){int locMain1 1, locMain2 2, locMain3 3;PRINT_ADDR(locMain1);PRINT_ADDR(locMain2);PRINT_ADDR(locMain3);StackFrameContent(locMain1, locMain2, locMain3);printf([locMain1,2,3] [%d, %d, %d]\n, locMain1, locMain2, locMain3);memset(locMain2, 0, 2*sizeof(int));printf([locMain1,2,3] [%d, %d, %d]\n, locMain1, locMain2, locMain3);return 0; }StackFrame 编译链接并执行后输出打印如下 函数栈布局示例如下图所示。为直观起见低于起始高地址0xbfc75a58的其他地址采用点记法如0x.54表示0xbfc75a54以此类推。 内存地址从栈底到栈顶递减压栈就是把ESP指针逐渐往地低址移动的过程。而结构体tStrt中的成员变量memberX地址tStrt首地址(memberX偏移量)即越靠近tStrt首地址的成员变量其内存地址越小。因此结构体成员变量的入栈顺序与其在结构体中声明的顺序相反。 函数调用以值传递时传入的实参(locMain1~3)与被调函数内操作的形参(para1~3)两者存储地址不同因此被调函数无法直接修改主调函数实参值(对形参的操作相当于修改实参的副本)。为达到修改目的需要向被调函数传递实参变量的指针(即变量的地址)。 此外[locMain1,2,3] [0, 0, 3]是因为对四字节参数locMain2调用memset函数时会从低地址向高地址连续清零8个字节从而误将位于高地址locMain1清零。 注意局部变量的布局依赖于编译器实现等因素。因此当StackFrameContent函数中删除打印语句时变量locVar3、locVar2和locVar1可能按照从高到低的顺序依次存储而且局部变量并不总在栈中有时出于性能(速度)考虑会存放在寄存器中。数组/结构体型的局部变量通常分配在栈内存中。 【扩展阅读】函数局部变量布局方式 与函数调用约定规定参数如何传入不同局部变量以何种方式布局并未规定。编译器计算函数局部变量所需要的空间总数并确定这些变量存储在寄存器上还是分配在程序栈上(甚至被优化掉)——某些处理器并没有堆栈。局部变量的空间分配与主调函数和被调函数无关仅仅从函数源代码上无法确定该函数的局部变量分布情况。 基于不同的编译器版本(gcc3.4中局部变量按照定义顺序依次入栈gcc4及以上版本则不定)、优化级别、目标处理器架构、栈安全性等相邻定义的两个变量在内存位置上可能相邻也可能不相邻前后关系也不固定。若要确保两个对象在内存上相邻且前后关系固定可使用结构体或数组定义。 4 堆栈操作 函数调用时的具体步骤如下 1) 主调函数将被调函数所要求的参数根据相应的函数调用约定保存在运行时栈中。该操作会改变程序的栈指针。 注x86平台将参数压入调用栈中。而x86_64平台具有16个通用64位寄存器故调用函数时前6个参数通常由寄存器传递其余参数才通过栈传递。 2) 主调函数将控制权移交给被调函数(使用call指令)。函数的返回地址(待执行的下条指令地址)保存在程序栈中(压栈操作隐含在call指令中)。 3) 若有必要被调函数会设置帧基指针并保存被调函数希望保持不变的寄存器值。 4) 被调函数通过修改栈顶指针的值为自己的局部变量在运行时栈中分配内存空间并从帧基指针的位置处向低地址方向存放被调函数的局部变量和临时变量。 5) 被调函数执行自己任务此时可能需要访问由主调函数传入的参数。若被调函数返回一个值该值通常保存在一个指定寄存器中(如EAX)。 6) 一旦被调函数完成操作为该函数局部变量分配的栈空间将被释放。这通常是步骤4的逆向执行。 7) 恢复步骤3中保存的寄存器值包含主调函数的帧基指针寄存器。 8) 被调函数将控制权交还主调函数(使用ret指令)。根据使用的函数调用约定该操作也可能从程序栈上清除先前传入的参数。 9) 主调函数再次获得控制权后可能需要将先前的参数从栈上清除。在这种情况下对栈的修改需要将帧基指针值恢复到步骤1之前的值。 步骤3与步骤4在函数调用之初常一同出现统称为函数序(prologue)步骤6到步骤8在函数调用的最后常一同出现统称为函数跋(epilogue)。函数序和函数跋是编译器自动添加的开始和结束汇编代码其实现与CPU架构和编译器相关。除步骤5代表函数实体外其它所有操作组成函数调用。 以下介绍函数调用过程中的主要指令。 压栈(push)栈顶指针ESP减小4个字节以字节为单位将寄存器数据(四字节不足补零)压入堆栈从高到低按字节依次将数据存入ESP-1、ESP-2、ESP-3、ESP-4指向的地址单元。 出栈(pop)栈顶指针ESP指向的栈中数据被取回到寄存器栈顶指针ESP增加4个字节。 可见压栈操作将寄存器内容存入栈内存中(寄存器原内容不变)栈顶地址减小出栈操作从栈内存中取回寄存器内容(栈内已存数据不会自动清零)栈顶地址增大。栈顶指针ESP总是指向栈中下一个可用数据。 调用(call)将当前的指令指针EIP(该指针指向紧接在call指令后的下条指令)压入堆栈以备返回时能恢复执行下条指令然后设置EIP指向被调函数代码开始处以跳转到被调函数的入口地址执行。 离开(leave) 恢复主调函数的栈帧以准备返回。等价于指令序列movl %ebp, %esp(恢复原ESP值指向被调函数栈帧开始处)和popl %ebp(恢复原ebp的值即主调函数帧基指针)。 返回(ret)与call指令配合用于从函数或过程返回。从栈顶弹出返回地址(之前call指令保存的下条指令地址)到EIP寄存器中程序转到该地址处继续执行(此时ESP指向进入函数时的第一个参数)。若带立即数ESP再加立即数(丢弃一些在执行call前入栈的参数)。使用该指令前应使当前栈顶指针所指向位置的内容正好是先前call指令保存的返回地址。 基于以上指令使用C调用约定的被调函数典型的函数序和函数跋实现如下 若主调函数和调函数均未使用局部变量寄存器EDI、ESI和EBX则编译器无须在函数序中对其压栈以便提高程序的执行效率。 参数压栈指令因编译器而异如下两种压栈方式基本等效 两种压栈方式均遵循C调用约定但方式二中主调函数在调用返回后并未显式清理堆栈空间。因为在被调函数序阶段编译器在栈顶为函数参数预先分配内存空间(sub指令)。函数参数被复制到栈中(而非压入栈中)并未修改栈顶指针故调用返回时主调函数也无需修改栈顶指针。gcc3.4(或更高版本)编译器采用该技术将函数参数传递至栈上相比栈顶指针随每次参数压栈而多次下移一次性设置好栈顶指针更为高效。设想连续调用多个函数时方式二仅需预先分配一次参数内存(大小足够容纳参数尺寸和最大的函数即可)后续调用无需每次都恢复栈顶指针。注意函数被调用时两种方式均使栈顶指针指向函数最左边的参数。本文不再区分两种压栈方式压栈或入栈所提之处均按相应汇编代码理解若无汇编则指方式二。 某些情况下编译器生成的函数调用进入/退出指令序列并不按照以上方式进行。例如若C函数声明为static(只在本编译单元内可见)且函数在编译单元内被直接调用未被显示或隐式取地址(即没有任何函数指针指向该函数)此时编译器确信该函数不会被其它编译单元调用因此可随意修改其进/出指令序列以达到优化目的。 尽管使用的寄存器名字和指令在不同处理器架构上有所不同但创建栈帧的基本过程一致。 注意栈帧是运行时概念若程序不运行就不存在栈和栈帧。但通过分析目标文件中建立函数栈帧的汇编代码(尤其是函数序和函数跋过程)即使函数没有运行也能了解函数的栈帧结构。通过分析可确定分配在函数栈帧上的局部变量空间准确值函数中是否使用帧基指针以及识别函数栈帧中对变量的所有内存引用。 5 函数调用约定 创建一个栈帧的最重要步骤是主调函数如何向栈中传递函数参数。主调函数必须精确存储这些参数以便被调函数能够访问到它们。函数通过选择特定的调用约定来表明其希望以特定方式接收参数。此外当被调函数完成任务后调用约定规定先前入栈的参数由主调函数还是被调函数负责清除以保证程序的栈顶指针完整性。 函数调用约定通常规定如下几方面内容 1) 函数参数的传递顺序和方式 最常见的参数传递方式是通过堆栈传递。主调函数将参数压入栈中被调函数以相对于帧基指针的正偏移量来访问栈中的参数。对于有多个参数的函数调用约定需规定主调函数将参数压栈的顺序(从左至右还是从右至左)。某些调用约定允许使用寄存器传参以提高性能。 2) 栈的维护方式 主调函数将参数压栈后调用被调函数体返回时需将被压栈的参数全部弹出以便将栈恢复到调用前的状态。该清栈过程可由主调函数负责完成也可由被调函数负责完成。 3) 名字修饰(Name-mangling)策略 又称函数名修饰(Decorated Name)规则。编译器在链接时为区分不同函数对函数名作不同修饰。 若函数之间的调用约定不匹配可能会产生堆栈异常或链接错误等问题。因此为了保证程序能正确执行所有的函数调用均应遵守一致的调用约定。 5.1 常见调用约定 下面分别介绍常见的几种函数调用约定。 1. cdecl调用约定 又称C调用约定是C/C编译器默认的函数调用约定。所有非C成员函数和未使用stdcall或fastcall声明的函数都默认是cdecl方式。函数参数按照从右到左的顺序入栈函数调用者负责清除栈中的参数返回值在EAX中。由于每次函数调用都要产生清除(还原)堆栈的代码故使用cdecl方式编译的程序比使用stdcall方式编译的程序大(后者仅需在被调函数内产生一份清栈代码)。但cdecl调用方式支持可变参数函数(即函数带有可变数目的参数如printf)且调用时即使实参和形参数目不符也不会导致堆栈错误。对于C函数cdecl方式的名字修饰约定是在函数名前添加一个下划线对于C函数除非特别使用extern CC函数使用不同的名字修饰方式。 【扩展阅读】可变参数函数支持条件 若要支持可变参数的函数则参数应自右向左进栈并且由主调函数负责清除栈中的参数(参数出栈)。 首先参数按照从右向左的顺序压栈则参数列表最左边(第一个)的参数最接近栈顶位置。所有参数距离帧基指针的偏移量都是常数而不必关心已入栈的参数数目。只要不定的参数的数目能根据第一个已明确的参数确定就可使用不定参数。例如printf函数第一个参数即格式化字符串可作为后继参数指示符。通过它们就可得到后续参数的类型和个数进而知道所有参数的尺寸。当传递的参数过多时以帧基指针为基准获取适当数目的参数其他忽略即可。若函数参数自左向右进栈则第一个参数距离栈帧指针的偏移量与已入栈的参数数目有关需要计算所有参数占用的空间后才能精确定位。当实际传入的参数数目与函数期望接受的参数数目不同时偏移量计算会出错 其次调用函数将参数压栈只有它才知道栈中的参数数目和尺寸因此调用函数可安全地清栈。而被调函数永远也不能事先知道将要传入函数的参数信息难以对栈顶指针进行调整。 C为兼容C仍然支持函数带有可变的参数。但在C中更好的选择常常是函数多态。 2. stdcall调用约定(微软命名) Pascal程序缺省调用方式WinAPI也多采用该调用约定。stdcall调用约定主调函数参数从右向左入栈除指针或引用类型参数外所有参数采用传值方式传递由被调函数负责清除栈中的参数返回值在EAX中。stdcall调用约定仅适用于参数个数固定的函数因为被调函数清栈时无法精确获知栈上有多少函数参数而且如果调用时实参和形参数目不符会导致堆栈错误。对于C函数stdcall名称修饰方式是在函数名字前添加下划线在函数名字后添加和函数参数的大小如_functionnamenumber。 3. fastcall调用约定 stdcall调用约定的变形通常使用ECX和EDX寄存器传递前两个DWORD(四字节双字)类型或更少字节的函数参数其余参数按照从右向左的顺序入栈被调函数在返回前负责清除栈中的参数返回值在 EAX 中。因为并不是所有的参数都有压栈操作所以比stdcall和cdecl快些。编译器使用两个修饰函数名字后跟十进制数表示的函数参数列表大小(字节数)如function_namenumber。需注意fastcall函数调用约定在不同编译器上可能有不同的实现比如16位编译器和32位编译器。另外在使用内嵌汇编代码时还应注意不能和编译器使用的寄存器有冲突。 4. thiscall调用约定 C类中的非静态函数必须接收一个指向主调对象的类指针(this指针)并可能较频繁的使用该指针。主调函数的对象地址必须由调用者提供并在调用对象非静态成员函数时将对象指针以参数形式传递给被调函数。编译器默认使用thiscall调用约定以高效传递和存储C类的非静态成员函数的this指针参数。 thiscall调用约定函数参数按照从右向左的顺序入栈。若参数数目固定则类实例的this指针通过ECX寄存器传递给被调函数被调函数自身清理堆栈若参数数目不定则this指针在所有参数入栈后再入栈主调函数清理堆栈。thiscall不是C关键字故不能使用thiscall声明函数它只能由编译器使用。 注意该调用约定特点随编译器不同而不同g中thiscall与cdecl基本相同只是隐式地将this指针当作非静态成员函数的第1个参数主调函数在调用返回后负责清理栈上参数而在VC中this指针存放在%ecx寄存器中参数从右至左压栈非静态成员函数负责清理栈上参数。 5. naked call调用约定 对于使用naked call方式声明的函数编译器不产生保存(prologue)和恢复(epilogue)寄存器的代码且不能用return返回返回值(只能用内嵌汇编返回结果)故称naked call。该调用约定用于一些特殊场合如声明处于非C/C上下文中的函数并由程序员自行编写初始化和清栈的内嵌汇编指令。注意naked call并非类型修饰符故该调用约定必须与__declspec同时使用如VC下定义求和函数 代码示例如下(Windows采用Intel汇编语法注释符为;) __declspec(naked) int __stdcall function(int a, int b) {;mov DestRegister, SrcImmediate(Intel) vs. movl $SrcImmediate, %DestRegister(ATT)__asm mov eax, a__asm add eax, b__asm ret 8 } 注意__declspec是微软关键字其他系统上可能没有。  6. pascal调用约定 Pascal语言调用约定参数按照从左至右的顺序入栈。Pascal语言只支持固定参数的函数参数的类型和数量完全可知故由被调函数自身清理堆栈。pascal调用约定输出的函数名称无任何修饰且全部大写。 Win3.X(16位)时支持真正的pascal调用约定而Win9.X(32位)以后pascal约定由stdcall约定代替(以C约定压栈以Pascal约定清栈)。 上述调用约定的主要特点如下表所示 Windows下可直接在函数声明前添加关键字__stdcall、__cdecl或__fastcall等标识确定函数的调用方式如int __stdcall func()。Linux下可借用函数attribute 机制如int __attribute__((__stdcall__)) func()。 代码示例如下 int __attribute__((__cdecl__)) CalleeFunc(int i, int j, int k){ // int __attribute__((__stdcall__)) CalleeFunc(int i, int j, int k){ //int __attribute__((__fastcall__)) CalleeFunc(int i, int j, int k){return ijk; } void CallerFunc(void){CalleeFunc(0x11, 0x22, 0x33); } int main(void){CallerFunc();return 0; } 被调函数CalleeFunc分别声明为cdecl、stdcall和fastcall约定时其汇编代码比较如下表所示 5.2 调用约定影响 当函数导出被其他程序员所使用(如库函数)时该函数应遵循主要的调用约定以便于程序员使用。若函数仅供内部使用则其调用约定可只被使用该函数的程序所了解。 在多语言混合编程(包括A语言中使用B语言开发的第三方库)时若函数的原型声明和函数体定义不一致或调用函数时声明了不同的函数约定将可能导致严重问题(如堆栈被破坏)。 以Delphi调用C函数为例。Delphi函数缺省采用stdcall调用约定而C函数缺省采用cdecl调用约定。一般将C函数声明为stdcall约定如int __stdcall add(int a, int b); 在Delphi中调用该函数时也应声明为stdcall约定 //参数类型应与DLL中的函数或过程参数类型一致且引用时使用stdcall参数 function add(a: Integer; b: Integer): Integer; stdcall; external a.dll; //指定被调DLL文件的路径和名称 不同编译器产生栈帧的方式不尽相同主调函数不一定能正常完成清栈工作而被调函数必然能自己完成正常清栈因此在跨(开发)平台调用中通常使用stdcall调用约定(不少WinApi均采用该约定)。 此外主调函数和被调函数所在模块采用相同的调用约定但分别使用C和C语法编译时会出现链接错误(报告被调函数未定义)。这是因为两种语言的函数名字修饰规则不同解决方式是使用extern C告知主调函数所在模块被调函数是C语言编译的。采用C语言编译的库应考虑到使用该库的程序可能是C程序(使用C编译器)通常应这样声明头文件 #ifdef _cplusplusextern C { #endiftype Func(type para); #ifdef _cplusplus} #endif 这样C编译器就会按照C语言修饰策略链接Func函数名而不会出现找不到函数的链接错误。 5.3 x86函数参数传递方法 x86处理器ABI规范中规定所有传递给被调函数的参数都通过堆栈来完成其压栈顺序是以函数参数从右到左的顺序。当向被调函数传递参数时所有参数最后形成一个数组。由于采用从右到左的压栈顺序数组中参数的顺序(下标0~N-1)与函数参数声明顺序(Para1~N)一致。因此在函数中若知道第一个参数地址和各参数占用字节数就可通过访问数组的方式去访问每个参数。 5.3.1 整型和指针参数的传递 整型参数与指针参数的传递方式相同因为在32位x86处理器上整型与指针大小相同(均为四字节)。下表给出这两种类型的参数在栈帧中的位置关系。注意该表基于tail函数的栈帧。 5.3.2 浮点参数的传递 浮点参数的传递与整型类似区别在于参数大小。x86处理器中浮点类型占8个字节因此在栈中也需要占用8个字节。下表给出浮点参数在栈帧中的位置关系。图中调用tail函数的第一个和第三个参数均为浮点类型因此需各占用8个字节三个参数共占用20个字节。表中word类型的大小是4字节。 5.3.3 结构体和联合体参数的传递 结构体和联合体参数的传递与整型、浮点参数类似只是其占用字节大小视数据结构的定义不同而异。x86处理器上栈宽是4字节故结构体在栈上所占用的字节数为4的倍数。编译器会对结构体进行适当的填充以使得结构体大小满足4字节对齐的要求。 对于一些RISC处理器(如PowerPC)其参数传递并不是全部通过栈来实现。PowerPC处理器寄存器中R3R10共8个寄存器用于传递整型或指针参数F1F8共8个寄存器用于传递浮点参数。当所需传递的参数少于8个时不需要用到栈。结构体和long double参数的传递通过指针来完成这与x86处理器完全不同。PowerPC的ABI规范中规定结构体的传递采用指针方式而不是像x86处理器那样将结构从一个函数栈帧中拷贝到另一个函数栈帧中显然x86处理器的方式更低效。可见PowerPC程序中函数参数采用指向结构体的指针(而非结构体)并不能提高效率不过通常这是良好的编程习惯。 5.4 x86函数返回值传递方法 函数返回值可通过寄存器传递。当被调用函数需要返回结果给调用函数时 1) 若返回值不超过4字节(如int、short、char、指针等类型)通常将其保存在EAX寄存器中调用方通过读取EAX获取返回值。 2) 若返回值大于4字节而小于8字节(如long long或_int64类型)则通过EAXEDX寄存器联合返回其中EDX保存返回值高4字节EAX保存返回值低4字节。 3) 若返回值为浮点类型(如float和double)则通过专用的协处理器浮点数寄存器栈的栈顶返回。 4) 若返回值为结构体或联合体则主调函数向被调函数传递一个额外参数该参数指向将要保存返回值的地址。即函数调用foo(p1, p2)被转化为foo(p0, p1, p2)以引用型参数形式传回返回值。具体步骤可能为a.主调函数将显式的实参逆序入栈b.将接收返回值的结构体变量地址作为隐藏参数入栈(若未定义该接收变量则在栈上额外开辟空间作为接收返回值的临时变量)c. 被调函数将待返回数据拷贝到隐藏参数所指向的内存地址并将该地址存入%eax寄存器。因此在被调函数中完成返回值的赋值工作。 注意函数如何传递结构体或联合体返回值依赖于具体实现。不同编译器、平台、调用约定甚至编译参数下可能采用不同的实现方法。如VC6编译器对于不超过8字节的小结构体会通过EAXEDX寄存器返回。而对于超过8字节的大结构体主调函数在栈上分配用于接收返回值的临时结构体并将地址通过栈传递给被调函数被调函数根据返回值地址设置返回值(拷贝操作)调用返回后主调函数根据需要再将返回值赋值给需要的临时变量(二次拷贝)。实际使用中为提高效率通常将结构体指针作为实参传递给被调函数以接收返回值。 5) 不要返回指向栈内存的指针如返回被调函数内局部变量地址(包括局部数组名)。因为函数返回后其栈帧空间被“释放”原栈帧内分配的局部变量空间的内容是不稳定和不被保证的。 函数返回值通过寄存器传递无需空间分配等操作故返回值的代价很低。基于此原因C89规范中约定不写明返回值类型的函数返回值类型默认为int。但这会带来类型安全隐患如函数定义时返回值为浮点数而函数未声明或声明时未指明返回值类型则调用时默认从寄存器EAX(而不是浮点数寄存器)中获取返回值导致错误因此在C中不写明返回值类型的函数返回值类型为void表示不返回值。 【扩展阅读】GCC返回结构体和联合体 通常GCC被配置为使用与目标系统一致的函数调用约定。这通过机器描述宏来实现。但是在一些目标机上采用不同方式返回结构体和联合体的值。因此使用PCC编译的返回这些类型的函数不能被使用GCC编译的代码调用反之亦然。但这并未造成麻烦因为很少有Unix库函数返回结构体或联合体。 GCC代码使用存放int或double类型返回值的寄存器来返回1、2、4或8个字节的结构体和联合体(GCC通常还将此类变量分配在寄存器中)。其它大小的结构体和联合体在返回时将其存放在一个由调用者传递的地址中(通常在寄存器中)。 相比之下PCC在大多目标机上返回任何大小的结构体和联合体时都将数据复制到一个静态存储区域再将该地址当作指针值返回。调用者必须将数据从那个内存区域复制到需要的地方。这比GCC使用的方法要慢而且不可重入。 在一些目标机上(如RISC机器和80386)标准的系统约定是将返回值的地址传给子程序。在这些机器上当使用这种约定方法时GCC被配置为与标准编译器兼容。这可能会对于124或8字节的结构体不兼容。 GCC使用系统的标准约定来传递参数。在一些机器上前几个参数通过寄存器传递在另一些机器上所有的参数都通过栈传递。原本可在所有机器上都使用寄存器来传递参数而且此法还可能显著提高性能。但这样就与使用标准约定的代码完全不兼容。所以这种改变只在将GCC作为系统唯一的C编译器时才实用。当拥有一套完整的GNU 系统能够用GCC来编译库时可在特定机器上实现寄存器参数传递。 在一些机器上(特别是SPARC)一些类型的参数通过“隐匿引用”(invisible reference)来传递。这意味着值存储在内存中将值的内存地址传给子程序。
http://www.yutouwan.com/news/85224/

相关文章:

  • 绵阳学校网站建设我国市级网站建设分析模板
  • wordpress怎么更改语言设置seo标题关键词优化
  • 网站维护更新WordPress图片分享插件
  • 公司网站建设维护及使用管理办法衡阳专业的关键词优化终报价
  • 动态广告怎么做出来的百度推广seo自学
  • 做视频直播的网站有哪些信誉好的大良网站建设
  • 网站建设合同 模板茶叶网站建设策划书ppt
  • 电商网站平台建设资金预算医院做网站的好处
  • 网站怎么屏蔽国内访问河南网站建设yipinpai
  • 装修设计图网站排名南江县规划和建设局网站
  • 网站模板设计工具宁波网络推广店
  • 专业的企业级cms建站系统校园网络拓扑图及网络方案
  • 站长之家域名查询鹿少女网站开发程序员是做什么的
  • 无锡做网站设计安微省住房和城乡建设厅网站
  • 网站开发目的简介wordpress code插件
  • 网站在空间费用昆明哪里做网站
  • 定制网站开发一般多少钱西安企业建站排名
  • 杭州公司建站模板网站建站合同
  • 站长工具外链查询外贸建站优化推广
  • 网站开发环境及工具女教师网课入06654侵录屏
  • 中小企业网络营销存在的问题研究海南seo顾问服务
  • 网站后台 编辑器 调用做网站 怎么提升浏览量
  • 网站开发知识体系泰安企业网站seo
  • 企业网站设计开题报告2022年注册公司流程
  • 旅游网站怎么用dw做贡井区建设局网站?
  • 导航网站能个人备案老外做汉字网站
  • 搭建网站免费html网站建设的步骤
  • 做网站的公司主要工作是什么吉林省建设信息网官网入口
  • 后台控制网站关键词设置的详细代码网络运维工程师任职要求
  • 东莞网站建设优化gta5买房网站正在建设