汇编调用c函数为什么要设置栈
汇编调用c函数为什么要设置栈

汇编调用c函数为什么要设置栈计算机领域,堆栈是一个不容忽视的概念,堆栈是两种数据结构。
堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行*入和删除。
在单片机应用中,堆栈是个特殊的存储区,主要功能是暂时存放数据和地址,通常用来保护断点和现场。
要点:堆,队列优先,先进先出。
栈,先进后出(First-In/Last-Out)。
汇编调用c函数为什么要设置栈,下面我们一起来看看。
(1)保存现场/上下文(2)传递参数:汇编代码调用c函数时,需传递参数(3)保存临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
之前看了很多关于uboot的分析,其中就有说要为C语言的运行,准备好栈。
而自己在Uboot的start.S汇编代码中,关于系统初始化,也看到有栈指针初始化这个动作。
但是,从来只是看到有人说系统初始化要初始化栈,即正确给栈指针sp赋值,但是却从来没有看到有人解释,为何要初始化栈。
所以,接下来的内容,就是经过一定的探究,试图来解释一下,为何要初始化栈。
要明白这个问题,首先要了解栈的作用。
关于栈的作用,要详细讲解的话,要很长的篇幅,所以此处只是做简略介绍。
总的来说,栈的作用就是:保存现场/上下文,传递参数,保存临时变量1.保存现场/上下文现场/上下文,意思就相当于案发现场,总有一些现场的情况,要记录下来的,否则被别人破坏掉之后,你就无法恢复现场了。
而此处说的现场,就是指CPU运行的时候,用到了一些寄存器,比如r0,r1等等,对于这些寄存器的值,如果你不保存而直接跳转到子函数中去执行,那么很可能就被其破坏了,因为其函数执行也要用到这些寄存器。
因此,在函数调用之前,应该将这些寄存器等现场,暂时保持起来(入栈push),等调用函数执行完毕返回后(出栈pop),再恢复现场。
这样CPU就可以正确的继续执行了。
保存寄存器的值,一般用的是push指令,将对应的某些寄存器的值,一个个放到栈中,把对应的值压入到栈里面,即所谓的压栈。
在汇编程序中调用C函数

在汇编程序中调用C函数
在汇编函数中要调用C语言的子函数,那么应该根据C 的函数原型所要求的参数类型,分别把参数压入堆栈后, 再调用C函数。调用结束后还需再进行弹栈,以恢复调用C 函数前的堆栈指针。此过程很容易产生bug,所以需要程序 员细心处理。下面的例子给出了汇编调用C函数的过程。
//**********************描述: 汇编调用C的函数 ***********************/ .EXTERNAL _F_Sub_C .CODE .PUBLIC _main; //========================函数: main() ========================= // 描述:主函数
//***************************************************/
第3页
2022/4/1
C语言子函数如下: //========================函数: F_Sub_C()====== //语法:void F_Sub_C(int i,int j,int k) //描述:延时程序 //参数:i,j,k //返回:i
第2页
2022/4/1
_main: R1 = 1; PUSH R1 TO [SP]; //第3个参数入栈
R1入栈
R1 = 3; PUSH R1 TO [SP]; //第1个参数入栈
CALL _F_Sub_C; POP R1,R3 FROM [SP]; GOTO _main; RETF; //*******************************************************/ //void F_Sub_C(int i,int j,int k); 来自于asm.c。延时程序,入口参数i,j,k; 返回i // main.asm 结束
简述栈的工作原理

简述栈的工作原理栈是计算机科学中一种重要的数据结构,它的工作原理可以简述为“先进后出”的原则。
栈的设计和实现使得它在各种计算机程序中扮演着重要的角色,包括编译器、操作系统和各种应用程序等。
栈可以看作是一种特殊的线性表,它只允许在表的一端进行插入和删除操作。
这一端被称为栈顶,另一端被称为栈底。
栈底固定,而栈顶可以随着插入和删除操作的进行而改变。
栈中的元素按照插入的先后顺序排列,最后插入的元素总是位于栈顶,而最先插入的元素总是位于栈底。
栈的插入操作被称为入栈,也被称为压栈或推栈。
入栈操作将一个新的元素放置在栈顶,同时栈顶向上移动一个位置。
栈的删除操作被称为出栈,也被称为弹栈。
出栈操作从栈顶删除一个元素,同时栈顶向下移动一个位置。
栈的工作原理可以用一个简单的例子来说明。
假设我们要对一串字符进行括号匹配的检查,即检查括号是否成对出现且嵌套正确。
我们可以使用栈来实现这个功能。
我们创建一个空栈。
然后,我们从左到右依次遍历字符串中的每个字符。
对于每个字符,如果它是一个左括号(如"("、"["或"{"),我们将其入栈;如果它是一个右括号(如")"、"]"或"}"),我们将其与栈顶的元素进行匹配。
如果栈顶的元素是相应的左括号,我们将栈顶的元素出栈;如果不匹配,或者栈为空,那么说明括号匹配出现错误。
最后,如果所有的字符都被处理完,并且栈为空,那么括号匹配是正确的;否则,括号匹配是错误的。
这个例子展示了栈的典型应用场景之一,即处理嵌套结构的问题。
栈的先进后出的特性使得它非常适合处理这类问题。
当我们需要记录嵌套结构的层次关系时,栈可以派上用场。
在上述例子中,栈记录了每个左括号的位置,使得我们可以在遇到右括号时快速找到相应的左括号。
除了括号匹配,栈还可以用来解决其他一些常见的问题,如逆序输出、函数调用和表达式求值等。
在汇编程序中调用C函数

3.4.2 在汇编程序中调用C函数从汇编程序中调用C语言函数的方法实际上在上面已经给出。
在上面C语言例子对应的汇编程序代码中,我们可以看出汇编程序语句是如何调用swap()函数的。
现在我们对调用方法作一总结。
在汇编程序调用一个C函数时,程序需要首先按照逆向顺序把函数参数压入栈中,即函数最后(最右边的)一个参数先入栈,而最左边的第1个参数在最后调用指令之前入栈,如图3-6所示。
然后执行CALL 指令去执行被调用的函数。
在调用函数返回后,程序需要再把先前压入栈中的函数参数清除掉。
调用函数时压入堆栈的参数在执行CALL指令时,CPU会把CALL指令的下一条指令的地址压入栈中(见图3-6中的EIP)。
如果调用还涉及代码特权级变化,那么CPU会进行堆栈切换,并且把当前堆栈指针、段描述符和调用参数压入新堆栈中。
由于Linux内核中只使用中断门和陷阱门方式处理特权级变化时的调用情况,并没有使用CALL指令来处理特权级变化的情况,因此这里对特权级变化时的CALL指令使用方式不再进行说明。
汇编中调用C函数比较"自由",只要是在栈中适当位置的内容就都可以作为参数供C函数使用。
这里仍然以图3-6中具有3个参数的函数调用为例,如果我们没有专门为调用函数func()压入参数就直接调用它的话,那么func()函数仍然会把存放EIP位置以上的栈中其他内容作为自己的参数使用。
如果我们为调用func()而仅仅明确地压入了第1、第2个参数,那么func()函数的第3个参数p3就会直接使用p2前的栈中内容。
在Linux 0.1x内核代码中就有几处使用了这种方式。
例如在kernel/sys_call.s汇编程序中第231行上调用copy_process()函数(kernel/fork.c中第68行)的情况。
在汇编程序函数_sys_fork中虽然只把5个参数压入了栈中,但是copy_process()却带有多达17个参数(见下面的程序)。
栈在C语言中的应用

栈在C语言中的应用栈(Stack)是一种常见的数据结构,它采用“后进先出”(Last-In-First-Out,简称LIFO)的原则。
在C语言中,栈可以通过数组或者链表来实现。
栈在C语言中有着广泛的应用,包括函数调用、表达式求值、内存管理等方面。
本文将介绍栈在C语言中的应用,并分析其实现原理和使用技巧。
一、栈的基本概念和原理栈是一种线性数据结构,它具有以下两个基本操作:1. 入栈(Push):将一个元素添加到栈顶。
2. 出栈(Pop):将栈顶元素删除并返回。
栈的实现可以使用数组或者链表,两种方式各有优劣。
使用数组实现的栈叫做顺序栈,使用链表实现的栈叫做链式栈。
顺序栈的实现思路是利用数组的连续存储空间,通过一个指针top指向栈顶元素。
入栈操作只需将元素放入top指向的位置并将top自增;出栈操作只需将top自减并返回top指向的元素。
链式栈的实现思路是利用链表的节点存储元素,并通过一个指针top指向栈顶节点。
入栈操作只需创建一个新节点,将元素存入节点并将该节点插入到链表头部,并更新top指针;出栈操作只需删除链表头部节点并返回其存储的元素。
二、栈在函数调用中的应用在C语言中,函数调用时需要保存当前函数的执行上下文,包括参数、局部变量和返回地址等。
这些信息需要在函数结束后能够被正确恢复,而栈正是实现这一功能的常用数据结构。
当一个函数被调用时,将其参数和局部变量存储在栈中。
栈顶指针指向刚进入函数时的位置,而栈底指针指向函数调用栈的底部。
当函数返回时,栈顶指针回退到初始位置,这样之前保存的局部变量和返回地址就可以被恢复。
下面是一个示例代码,演示了栈在函数调用中的应用:```c#include <stdio.h>void func2();void func1(){int x = 1;printf("In func1: x = %d\n", x);func2();printf("Back to func1: x = %d\n", x);}void func2(){int y = 2;printf("In func2: y = %d\n", y);}int main(){func1();return 0;}```该代码中,函数`func1`调用了函数`func2`,并在函数内定义了一个局部变量`x`。
简述栈指令的功能

简述栈指令的功能栈指令,作为计算机指令中的一种类型,扮演着重要的角色。
它们被广泛应用于计算机系统的内存管理、函数调用以及各种算法和数据结构的实现中。
在本文中,我们将简要讨论栈指令的功能及其在计算机系统中的应用。
首先,让我们先了解一下栈的概念。
栈是一种基于后进先出(Last In First Out,LIFO)原则的数据结构。
它将数据以一种特定的方式存储和访问,只允许在栈顶进行插入和删除操作。
这种特殊的结构使得栈在实现各种算法和数据结构时非常有用。
栈指令的功能主要包括数据的入栈和出栈,以及栈指针的移动和操作。
首先,入栈操作将数据放入栈中的栈顶位置,同时栈指针向上移动一个位置;而出栈操作则是从栈顶位置取出数据,并将栈指针向下移动一个位置。
这两种操作相互配合,使得栈可以有效地管理数据的存储和访问。
栈指令在函数调用中扮演着重要的角色。
当我们调用一个函数时,计算机需要保存当前函数的现场信息,如函数的局部变量、参数以及返回地址等。
栈的特性使得它非常适合用于存储这些信息。
在函数调用过程中,相关的数据会被依次入栈,形成一个“调用栈帧”,而在函数返回时则通过出栈恢复现场信息,使得程序可以继续执行。
此外,栈指令也广泛应用于内存管理中。
在计算机系统中,栈通常被用来管理变量的生命周期和内存的分配。
当一个变量被定义时,它会被分配一个内存地址,而栈指令可以用来控制变量的入栈和出栈。
在变量的作用域结束时,通过出栈操作释放变量所占用的内存,从而避免了内存泄漏的问题。
此外,栈指令还常用于算法和数据结构的实现中。
例如,深度优先搜索(DFS)算法中可以使用栈来实现搜索过程中的状态回溯;括号匹配问题可以使用栈来判断括号是否匹配;逆波兰表达式的计算可以利用栈来实现等等。
这些应用进一步展示了栈指令在计算机科学领域中的重要性。
简而言之,栈指令的功能包括数据的入栈和出栈操作,以及对栈指针的移动和操作。
它们在计算机系统的内存管理、函数调用以及各种算法和数据结构的实现中发挥着重要的作用。
汇编语言堆栈指令

汇编语言堆栈指令汇编语言是一种底层的计算机语言,它直接操作计算机的硬件。
在汇编语言中,堆栈(Stack)是一种重要的数据结构,用于存储程序执行时的临时数据和返回地址等信息。
堆栈指令用于操作堆栈,包括入栈、出栈、压栈和弹栈等操作。
本文将从堆栈指令的角度介绍汇编语言的相关知识。
一、入栈指令入栈指令用于将数据压入堆栈,常用的入栈指令有PUSH和PUSHA。
PUSH指令可以将立即数或寄存器中的值压入堆栈,而PUSHA指令可以将通用寄存器中的值一次性压入堆栈。
入栈指令的作用是保存临时数据,以便后续的操作使用。
二、出栈指令出栈指令用于将数据从堆栈中弹出,常用的出栈指令有POP和POPA。
POP指令可以将堆栈顶部的数据弹出并存入指定的寄存器,而POPA 指令可以一次性将堆栈中的数据弹出并存入通用寄存器。
出栈指令的作用是恢复之前保存的数据,以便继续执行程序。
三、堆栈指针堆栈指针(Stack Pointer)是一个特殊的寄存器,用于指示当前堆栈的顶部位置。
在x86架构中,堆栈指针通常用ESP表示。
入栈和出栈指令会自动更新堆栈指针的值,以保证数据正确地压入和弹出堆栈。
四、压栈和弹栈压栈和弹栈是堆栈操作中的两个重要概念。
压栈(Push)指的是将数据从数据段移动到堆栈段的过程,堆栈指针会自动减小。
弹栈(Pop)指的是将数据从堆栈段移动到数据段的过程,堆栈指针会自动增加。
压栈和弹栈是堆栈操作的基本操作,用于实现数据的存储和读取。
五、堆栈的应用堆栈在汇编语言中有着广泛的应用,它可以用于实现函数的调用和返回、保存寄存器的状态、传递参数和局部变量等。
函数的调用和返回是汇编语言程序中常见的操作,它们依赖于堆栈来传递参数和保存返回地址。
当一个函数被调用时,参数会被压入堆栈,函数执行完毕后,返回地址会从堆栈中弹出,程序继续执行返回地址指向的位置。
堆栈还可以用于保存寄存器的状态。
在汇编语言中,为了保护现场,程序在执行前会将当前寄存器的值保存到堆栈中,执行完毕后再将堆栈中的值恢复到寄存器中。
栈的应用及特性

栈的应用及特性栈是计算机科学中一种非常重要的数据结构,具有广泛的应用和独特的特性。
下面将详细介绍栈的应用及特性。
一、栈的应用:1. 函数调用:在程序执行过程中,函数的调用和返回通常采用栈进行管理。
当一个函数被调用时,函数的参数和局部变量被压入栈中,函数执行完毕后,这些信息会被弹出栈恢复到调用函数的状态。
2. 表达式求值:在编程语言中,栈可用于表达式求值、中缀表达式转换为后缀表达式等相关操作。
通过利用栈的先进后出特性,可以方便地实现这些功能。
3. 递归算法:递归算法中的递归调用也可以通过栈来实现。
当算法需要递归调用时,将函数和相关变量的信息压入栈中,等到递归结束后,再从栈中弹出恢复状态。
4. 括号匹配:栈也常用于判断表达式中的括号是否匹配。
遍历表达式,遇到左括号时压入栈,遇到右括号时弹出栈顶元素,如果匹配则继续,不匹配则判定为括号不匹配。
5. 浏览器的前进后退:浏览器的前进后退功能可以使用栈实现。
每次浏览一个网页时,将该网页的URL压入栈中,点击后退按钮时,再从栈中弹出上一个URL,即可实现返回上一个网页的功能。
6. 撤销操作:在图形界面软件中,通常会有撤销操作。
使用栈可以将每一步操作的状态依次压入栈中,当用户需要撤销时,再从栈中弹出最近的状态,恢复到之前的操作状态。
二、栈的特性:1. 先进后出:栈是一种后进先出(LIFO)的数据结构,即最新添加的元素最先被访问或者删除。
这一特性使得栈能够方便地实现函数调用和返回等操作。
2. 只能操作栈顶元素:由于栈的特性,只能访问或者修改栈顶元素,无法直接访问或者修改栈中的其他元素。
需要先将栈顶元素弹出后,才能访问或者修改下一个栈顶元素。
3. 顺序存储结构:栈可以使用数组或者链表实现。
使用数组实现时,需要指定栈的最大容量,而使用链表实现时,没有容量限制。
4. 操作复杂度:栈的插入和删除操作只涉及栈顶元素,所以其操作复杂度为O(1)。
但是栈的搜索和访问操作需要从栈顶开始遍历,所以其操作复杂度为O(n)。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
一.栈的整体作用
(1)保存现场/上下文
(2)传递参数:汇编代码调用c函数时,需传递参数
(3)保存临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
二.为什么汇编代码调用c函数需要设置栈
之前看了很多关于uboot的分析,其中就有说要为C语言的运行,准备好栈。
而自己在Uboot 的start.S汇编代码中,关于系统初始化,也看到有栈指针初始化这个动作。
但是,从来只是看到有人说系统初始化要初始化栈,即正确给栈指针sp赋值,但是却从来没有看到有人解释,为何要初始化栈。
所以,接下来的内容,就是经过一定的探究,试图来解释一下,为何要初始化栈。
要明白这个问题,首先要了解栈的作用。
关于栈的作用,要详细讲解的话,要很长的篇幅,所以此处只是做简略介绍。
总的来说,栈的作用就是:保存现场/上下文,传递参数,保存临时变量
1.保存现场/上下文
现场/上下文,意思就相当于案发现场,总有一些现场的情况,要记录下来的,否则被别人破坏掉之后,你就无法恢复现场了。
而此处说的现场,就是指CPU运行的时候,用到了一些寄存器,比如r0,r1等等,对于这些寄存器的值,如果你不保存而直接跳转到子函数中去执行,那么很可能就被其破坏了,因为其函数执行也要用到这些寄存器。
因此,在函数调用之前,应该将这些寄存器等现场,暂时保持起来(入栈push),等调用函数执行完毕返回后(出栈pop),再恢复现场。
这样CPU就可以正确的继续执行了。
保存寄存器的值,一般用的是push指令,将对应的某些寄存器的值,一个个放到栈中,把对应的值压入到栈里面,即所谓的压栈。
然后待被调用的子函数执行完毕的时候,再调用pop,把栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从栈中弹出去,即所谓的出栈。
其中保存的寄存器中,也包括lr的值(因为用bl指令进行跳转的话,那么之前的pc的值是存在lr中的),然后在子程序执行完毕的时候,再把栈中的lr的值pop出来,赋值给pc,这样就实现了子函数的正确的返回。
2.传递参数
C语言进行函数调用的时候,常常会传递给被调用的函数一些参数,对于这些C语言级别的参数,被编译器翻译成汇编语言的时候,就要找个地方存放一下,并且让被调用的函数能够访问,否则就没发实现传递参数了。
对于找个地方放一下,分两种情况。
一种情况是,本身传递的参数不多于4个,就可以通过寄存器传送参数。
因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数。
另一种情况是,参数多于4个时,寄存器不够用,就得用栈了。
3.临时变量保存在栈中
包括函数的非静态局部变量以及编译器自动生成的其他临时变量。