精华内容
下载资源
问答
  • 地址函数调用
    万次阅读 多人点赞
    2017-03-18 16:47:36

    我想,你只要看了C语言上关于传值函数调用的测试题,一切都会了然于胸:

    1. 考题一:程序代码如下:
      void Exchg1(int x, int y)
      {
      int tmp;
      tmp=x;
      x=y;
      y=tmp;
      printf(“x=%d,y=%d/n”,x,y)
      }
      void main()
      {
      int a=4,b=6;
      Exchg1 (a,b) ;
      printf(“a=%d,b=%d/n”,a,b)
      }
      输出的结果:
      x=_, y=_
      a=_, b=_
      问下划线的部分应是什么,请完成。

    2. 考题二:代码如下。
      Exchg2(int *px, int *py)
      {
      int tmp=*px;
      *px=*py;
      *py=tmp;
      print(“*px=%d,*py=%d/n”,*px,*py);
      }
      main()
      {
      int a=4;
      int b=6;
      Exchg2(&a,&b);
      Print(“a=%d,b=%d/n”, a, b);
      }
      输出的结果为:
      *px=_, *py=_
      a=_, b=_
      问下划线的部分应是什么,请完成。

    3. 考题三:
      Exchg2(int &x, int &y)
      {
      int tmp=x;
      x=y;
      y=tmp;
      print(“x=%d,y=%d/n”,x,y);
      }
      main()
      {
      int a=4;
      int b=6;
      Exchg2(a,b);
      Print(“a=%d,b=%d/n”, a, b);
      }

    二. 函数参数传递方式之一:值传递

    1. 值传递的一个错误认识
    先看题一中Exchg1函数的定义:
    void Exchg1(int x, int y) //定义中的x,y变量被称为Exchg1函数的形式参数
    {
    int tmp;
    tmp=x;
    x=y;
    y=tmp;
    printf(“x=%d,y=%d/n”,x,y)
    }
    问:你认为这个函数是在做什么呀?
    答:好像是对参数x,y的值对调吧?
    请往下看,我想利用这个函数来完成对a,b两个变量值的对调,程序如下:
    void main()
    {
    int a=4,b=6;
    Exchg1 (a,b) //a,b变量为Exchg1函数的实际参数。
    / printf(“a=%d,b=%d/n”,a,b)
    }
    我问:Exchg1 ()里头的 printf(“x=%d,y=%d/n”,x,y)语句会输出什么啊?
    我再问:Exchg1 ()后的 printf(“a=%d,b=%d/n”,a,b)语句输出的是什么?
    程序输出的结果是:
    x=6 , y=4
    a=4 , b=6 //为什么不是a=6,b=4呢?

    奇怪,明明我把a,b分别代入了x,y中,并在函数里完成了两个变量值的交换,为什么a,b变量值还是没有交换(仍然是a==4,b==6,而不是a==6,b==4)?如果你也会有这个疑问,那是因为你跟本就不知实参a,b与形参x,y的关系了。
    

    2. 一个预备的常识
    为了说明这个问题,我先给出一个代码:
    int a=4;
    int x;
    x=a;
    x=x+3;
    看好了没,现在我问你:最终a值是多少,x值是多少?
    (怎么搞的,给我这个小儿科的问题。还不简单,不就是a==4 x==7嘛!)
    在这个代码中,你要明白一个东西:虽然a值赋给了x,但是a变量并不是x变量哦。我们对x任何的修改,都不会改变a变量。呵呵!虽然简单,并且一看就理所当然,不过可是一个很重要的认识喔。

    3. 理解值传递的形式
    看调用Exch1函数的代码:

    main()
    {
    int a=4,b=6;
    Exchg1(a,b) //这里调用了Exchg1函数
    printf(“a=%d,b=%d”,a,b)
    }

    Exchg1(a,b)时所完成的操作代码如下所示。
    int x=a;//←
    int y=b;//←注意这里,头两行是调用函数时的隐含操作
    int tmp;
    tmp=x;
    x=y;
    y=tmp;
    请注意在调用执行Exchg1函数的操作中我人为地加上了头两句:
    int x=a;
    int y=b;
    这是调用函数时的两个隐含动作。它确实存在,现在我只不过把它显式地写了出来而已。问题一下就清晰起来啦。(看到这里,现在你认为函数里面交换操作的是a,b变量或者只是x,y变量呢?)
    原来 ,其实函数在调用时是隐含地把实参a,b 的值分别赋值给了x,y,之后在你写的Exchg1函数体内再也没有对a,b进行任何的操作了。交换的只是x,y变量。并不是a,b。当然a,b的值没有改变啦!函数只是把a,b的值通过赋值传递给了x,y,函数里头操作的只是x,y的值并不是a,b的值。这就是所谓的参数的值传递了。
    哈哈,终于明白了,正是因为它隐含了那两个的赋值操作,才让我们产生了前述的迷惑(以为a,b已经代替了x,y,对x,y的操作就是对a,b的操作了,这是一个错误的观点啊!)。

    三. 函数参数传递方式之二:地址传递
    继续——地址传递的问题!
    看题二的代码:
    Exchg2(int *px, int *py)
    {
    int tmp=*px;
    *px=*py;
    *py=tmp;
    print(“*px=%d,*py=%d/n”,*px,*py);
    }
    main()
    {
    int a=4;
    int b=6;
    Exchg2(&a,&b);
    Print(“a=%d,b=%d/n”, a, b);
    }
    它的输出结果是:
    *px=6,*py=4
    a=6,b=4

    看函数的接口部分:Exchg2(int *px,int *py),请注意:参数px,py都是指针。
    再看调用处:Exchg2(&a, &b);
    它将a的地址(&a)代入到px,b的地址(&b)代入到py。同上面的值传递一样,函数调用时作了两个隐含的操作:将&a,&b的值赋值给了px,py。
    

    px=&a;
    py=&b;
    呵呵!我们发现,其实它与值传递并没有什么不同,只不过这里是将a,b的地址值传递给了px,py,而不是传递的a,b的内容,而(请好好地在比较比较啦)
    整个Exchg2函数调用是如下执行的:
    px=&a; //
    py=&b; //请注意这两行,它是调用Exchg2的隐含动作。
    int tmp=*px;
    *px=*py;
    *py=tmp;
    print(“*px=%d,*py=%d/n”,*px,*py);
    这样,有了头两行的隐含赋值操作。我们现在已经可以看出,指针px,py的值已经分别是a,b变量的地址值了。接下来,对*px,*py的操作当然也就是对a,b变量本身的操作了。所以函数里头的交换就是对a,b值的交换了,这就是所谓的地址传递(传递a,b的地址给了px,py),你现在明白了吗?

    四. 函数参数传递方式之三:引用传递
    看题三的代码:
    Exchg3(int &x, int &y) //注意定义处的形式参数的格式与值传递不同
    {
    int tmp=x;
    x=y;
    y=tmp;
    print(“x=%d,y=%d/n”,x,y);
    }
    main()
    {
    int a=4;
    int b=6;
    Exchg3(a,b); //注意:这里调用方式与值传递一样
    Print(“a=%d,b=%d/n”, a, b);
    }
    输出结果:
    x=6, y=4
    a=6, b=4 //这个输出结果与值传递不同。
    看到没有,与值传递相比,代码格式上只有一处是不同的,即在定义处:
    Exchg3(int &x, int &y)。
    但是我们发现a与b的值发生了对调。这说明了Exchg3(a,b)里头修改的是a,b变量,而不只是修改x,y了。
    我们先看Exchg3函数的定义处Exchg3(int &x,int &y)。参数x,y是int的变量,调用时我们可以像值传递(如: Exchg1(a,b); )一样调用函数(如: Exchg3(a,b); )。但是x,y前都有一个取地址符号&。有了这个,调用Exchg3时函数会将a,b 分别代替了x,y了,我们称x,y分别引用了a,b变量。这样函数里头操作的其实就是实参a,b本身了,也就是说函数里是可以直接修改到a,b的值了。

    最后对值传递与引用传递作一个比较:

    1. 在函数定义格式上有不同:
    值传递在定义处是:Exchg1(int x, int y);
    引用传递在这义处是:Exchg1(int &x, int &y);

    2. 调用时有相同的格式:
    值传递:Exchg1(a,b);
    引用传递:Exchg3(a,b);

    3. 功能上是不同的:
    值传递的函数里操作的不是a,b变量本身,只是将a,b值赋给了x,y函数里操作的只是x,y变量而不是a,b,显示a,b的值不会被Exchg1函数所修改。
    引用传递Exchg3(a,b)函数里是用a,b分别代替了x,y。函数里操作的是a,b。

    转自:http://blog.csdn.net/xiaosong2008/article/details/25430261

    更多相关内容
  • 栈帧和函数调用(一)一,栈帧的介绍二,函数调用与栈的关系三,汇编演示      在计算机科学中,栈是一个特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据...

    (栈帧和函数调用一)栈帧,函数调用与栈的关系

         在计算机科学中,栈是一个特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但栈容器必须准守一个规则:先入栈的数据后出栈(First In Last Out,FIFO)。
         在计算机系统中,栈是一个具有以上属性的动态内存区域(这里的动态指的是栈的空间可以动态增长或减小)。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈使得栈空间增大,出栈使得栈空间减小。栈的空间总是自顶向下增长的(由高地址向低地址扩展)。

    一,栈帧的介绍

         栈在程序空间中有着非常重要的地位,栈中保存了一个函数调用所需的维护信息,这就是我们常说的栈帧,栈帧一般包括以下几方面内容:
    1) 函数的返回地址(用于返回函数调用处)和参数
    2) 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量
    3) 保存的上下文:包括在函数调用前后需要保持不变的寄存器。
         在i386中,一个函数的活动记录范围由ebp和esp两个寄存器来
    划定,其中esp寄存器始终指向栈顶,同时也指向当前函数的活动记录的顶部;ebp寄存器指向栈底。ebp又被称为帧指针,esp被称为栈指针。
         栈帧是是相对于每一个函数调用而言的,是独立的。

    二,函数调用与栈的关系

    在这里插入图片描述
         一个函数的调用分为以下几步;
    1) 将函数参数按照函数定义约定依次压入栈中(_stdcall,_cdcel等)
    2) 将当前指令的下一条指令(也就是函数返回地址)压入栈中
    3) 跳转到函数体执行函数
    4) 栈帧调整:
    a) Push ebp:将ebp压入栈中,(old ebp)
    b) Move sbp,esp:将esp的值赋给ebp,这时ebp指向栈顶位置,而栈顶就是old ebp
    c) Sub esp,XXX栈上分配临时空间,push XXX/pop XXX保存某些寄存器的值
    d) mov eax, xxx:保存被调用函数的返回值到寄存器eax中
    e) move esp,ebp:恢复esp栈顶指针,同时回收栈上的局部变量
    f) pop ebp:恢复ebp栈底指针
    g) ret:从栈中取出返回地址,并jump到该地址

    三,汇编演示

         以一个简单的函数为例:

    Int fun()
    {
    	return 1;
    }
    

         对该函数反汇编结果如下:(vs编译器调试在函数入口处打断点,调试-窗口-反汇编)
    在这里插入图片描述
         第1-2行,保存了旧的ebp,并让ebp指向当前的栈顶。
         第3行,将esp栈顶扩展了0XC0个字节,这一大段空出来的空间用来存储局部变量,临时数据或者调试信息。
         第4-6行,将ebx,esi,edi三个寄存器的值保存到了栈上,这三个寄存器的值在函数随后的执行中可能被修改,所以要先保存一下这些寄存器原本的值,以便退出函数时恢复。
         第7-12行,也就是return 1之前是一些调试信息,其中请注意有一步是将eax赋值为0xCCCCCCCC,而eax是用来接受函数返回值的寄存器。
         第13行,将函数返回值1赋值给eax寄存器。
         第14-16行,从栈上恢复ebx,esi,edi三个寄存器的值
         第17行,将esp值加上0XC0个字节,恢复到原先的栈顶
         第18-21行,ebp出栈,恢复原先的栈底
         第22行,使用ret指令返回。

    四,总结

    1,栈帧是一个函数活动的记录,通过栈帧我们可以进行函数调用参数传递,以及函数返回值调用路径的回溯。
    2,函数的返回值是保存在eax寄存器中,在i386中,它是一个32位的寄存器,占4个字节(那么大于4个字节的函数返回值如何传递呢?本系列文章后续揭晓)
    3,函数调用的过程中,有进行栈的扩容操作,用于存放函数的入参,临时变量等一些信息,当函数调用结束后,这一部分栈空间会通过栈顶指针(esp寄存器)的移动被回收掉。

    参考文献:
    1,《程序员的自我修养:链接、装载与库》第十章第二节

    展开全文
  • MATLAB函数调用

    万次阅读 2021-01-07 20:57:42
    数学建模 matlab自定义函数 时间 2020年5月10日 ...2.两个文件运行函数:函数定义文件和函数调用文件(函数可在脚本文件或命令窗口直接调用),两文件必须放在同一目录下,函数文件名必须与函数名相...
    数学建模matlab自定义函数
    时间2020年5月10日

    学习Matlab自定义函数使用,并结合所学函数简单修改了一下上周的代码实现了Topsis法。

    1.Matlab语句构成的程序文件称为M文件,以m作为文件的扩展名,分为函数文件和程序文件。

    程序文件即脚本文件,无function;

    函数文件有function,且在第一行或者第一个不是注释的行

    2.两个文件运行函数:函数定义文件和函数调用文件(函数可在脚本文件或命令窗口直接调用),两文件必须放在同一目录下,函数文件名必须与函数名相同。

    1)一个参数:

    定义形式:

     function y=f(x) 
    
              y=x^2; 
    

    2)多个参数:

    多元函数可以看做向量函数(四种形式实现同一题的运算)

    函数定义形式(两个输入两个输出)

    function  [y1,y2]=f1(x1,x2)
    
              y1=x1+x2;
    
              y2=x1-x2;
    
    end
    

    函数调用形式

    [a,b]=f1(2,3)     
    
    函数定义形式(将两个输入合成一个向量)
    
    function  [y1,y2]=f1(x)
    
              y1=x(1)+x(2);
    
              y2=x(1)-x(2);
    
    end
    

    函数调用形式

    [a,b]=f2([2,3])
    

    函数定义形式(将两个输出合成一个向量)

    function y=f3(x1,x2)
    
            y(1)=x1+x2;
    
            y(2)=x1-x2;
    
    end
    

    函数调用形式

    c=f3(2,3)
    

    函数定义形式(将两个输入合成一个向量,将两个输出合成一个向量)

    function  y=f4(x)
    
            y(1)=x(1)+x(2);
    
            y(2)=x(1)-x(2);
    
    end
    

    函数调用形式

    d=f4([2,3])
    

    3)任意多个参数的情况:

    a.参数

    varargin任意多个输入参数
    
    varargout任意多个输出参数
    
    nargin得到实际输入参数个数
    
    nargout得到实际输出参数个数
    

    b.对于输入变量,MATLAB可以识别输入变量的个数,通过nargin来记录当前输入变量个数。通过nargin判断变量个数,然后再写对应的程序

    c.对于输出变量,MATLAB可以根据调用情况来输出结果,根据调用的格式,自动忽略没匹配上的输出结果。

    d.函数做好之后,需要写好说明,否则代码的通用性会很差。matlab自动将函数下的注释当作函数说明,通过help来查看帮助文档

    4)写一个函数计算输入参数之和(输入参数可以是任意多个)

    function y=ssum(varargin)
    
       n=nargin
    
       y=0;
    
       for i=1:n
    
       y=varargin{i}+y;
    
    end
    

    5)对于一组数据 x1,x2,…,xm, 分高优指标、低优指标、中优指标、均优指标,四种标准化方式对数据标准化。(学习代码并理解nargin的用法)

    function [x] = standlize(v,type,q)
    
    %STANDLIZE   对列向量v 进行归一化 返回归一化后的向量 x
    
    %  x = STANDLIZE(v,'low') 对列向量按低优指标进行归一化
    
    %  x = STANDLIZE(v,'high') 对列向量按高优指标进行归一化
    
    %  x = STANDLIZE(v,'middle',q) 对列向量按中优指标进行归一化
    
    %  x = STANDLIZE(v,'between',q) 其中 q = [a , b] 对列向量按均优指标进行归一化
    
    m = length(v);
    
    if nargin==2%两个输入参数,一个是向量V,另一个是指标类型
    
        if strcmp(type,'low') %低优指标
    
            %[strcmp](https://ww2.mathworks.cn/help/matlab/ref/strcmp.html)
    
            for i=1:m
    
                x(i) = (max(v)-v(i))/(max(v)-min(v));
    
            end
    
        elseif  strcmp(type,'high') %高优指标
    
            for i=1:m
    
                x(i) = (v(i)-min(v))/(max(v)-min(v));
    
              end
    
        end
    
    elseif nargin==3
    
            %输入变量有三个,第一个是向量V、第二个是指标类型、第三个是q;
    
            %若为中优指标,q用于存放中间值;
    
            %若为均优指标(即我们所学的区间型指标),q用于存放均优指标的区间( q = [a , b] )。
    
            %因此可结合q的长度来区分中优指标和均优指标。
    
        if strcmp(type,'middle')&&length(q)==1 %中优指标
    
            for i=1:m
    
                 if v(i) < q
    
                    x(i) = 1 - (q-v(i))/max([q-min(v),max(v)-q]);
    
                elseif  v(i) > q
    
                        x(i)=1- (v(i)-q)/max([q-min(v),max(v)-q]);                
    
    else
    
                    x(i) = 1;
    
                end
    
            end
    
        elseif strcmp(type,'between')&&length(q)==2 %均优指标
    
            for i=1:m
    
                if v(i) < q(1)
    
                    x(i) = 1-(q(1)-v(i))/(max([q(1)-min(v),max(v)-q(2)]));
    
                elseif v(i) > q(2)
    
                    x(i) = 1-(v(i)-q(2))/(max([q(1)-min(v),max(v)-q(2)]));
    
                else
    
                    x(i)=1;
    
                end
    
            end
    
        end
    
    end
    
    x = reshape(x,size(v));
    
            %访问链接查看[reshape](https://blog.csdn.net/weixin_33805557/article/details/94506302)
    
            
    end  
    

    简单了解matlab的绘图函数(条形图)

    % clear all;
    
    data=S;%代入数据
    
    b=bar(data,'BarWidth',0.6);%可以直接设置条形图的宽度
    
    grid on;
    
            %grid是显示或隐藏坐标轴网格线,grid on是打开网格,grid off是关闭网格。
    
    set(gca,'XTick',1:25,'XTickLabel',{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y'})%设置横坐标数值
    
    for i = 1:25
    
    text(i,S(i)+0.1,num2str(data(i)),'VerticalAlignment','middle','HorizontalAlignment','center','FontSize',12);%为条形图添加数标
    
    end
    
    set(gca,'FontSize',9);%设置字体大小
    
    xlabel('评价对象/河流');%横坐标标签
    
    ylabel('评分');%纵坐标标签
    
    axis([0 26 0 0.1]);%设置坐标轴显示范围
    
            %axis一般用来设置axes的样式,包括坐标轴范围,可来读比例等,
    
             %这儿用来设置坐标轴的范围,axis([xmin xmax ymin ymax])
    
    展开全文
  • 汇编:函数调用

    千次阅读 2019-07-18 18:57:33
    让我们再回忆一下脑海中关于函数调用的概念,也许会是这个样子: 这里的“控制流转移”又是如何发生的呢?在解释这个之前,也许我们需要科普一点有关于汇编的知识。 2. 函数调用中的一些细节说明 2.1 函数调用中的...

    1. 从代码的顺序执行说起

    每一个程序员脑子里应该都有这么一种印象:“程序是顺序执行的”。这个观点其实和我们开篇所讲的cpu的流水线执行过程直接相关。
    让我们再回忆一下脑海中关于函数调用的概念,也许会是这个样子:
    Paste_Image.png

    这里的“控制流转移”又是如何发生的呢?在解释这个之前,也许我们需要科普一点有关于汇编的知识。

    2. 函数调用中的一些细节说明

    2.1 函数调用中的关键寄存器

    2.1.1 程序计数器PC

    程序计数器是一个计算机组成原理中讲过的概念,下面给出一个百度百科中的简单解释

    程序计数器是用于存放下一条指令所在单元的地址的地方。
    当执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。

    可以看到,程序计数器是一个cpu执行指令代码过程中的关键寄存器:它指向了当前计算机要执行的指令地址,CPU总是从程序计数器取出当前指令来执行。当指令执行后,程序计数器的值自动增加,指向下一条将要执行的指令。
    在x86汇编中,执行程序计数器功能的寄存器被叫做EIP,也叫作指令指针寄存器。

    2.1.2 基址指针,栈指针和程序栈

    栈是程序设计中的一种经典数据结构,每个程序都拥有自己的程序栈。很重要的一点是,栈是向下生长的。所谓向下生长是指从内存高地址->低地址的路径延伸,那么就很明显了,栈有栈底和栈顶,那么栈顶的地址要比栈底低。对x86体系的CPU而言,其中
    —> 寄存器ebp(base pointer )可称为“帧指针”或“基址指针”,其实语意是相同的。
    —> 寄存器esp(stack pointer)可称为“ 栈指针”。
    在C和C++语言中,临时变量分配在栈中,临时变量拥有函数级的生命周期,即“在当前函数中有效,在函数外无效”。这种现象就是函数调用过程中的参数压栈,堆栈平衡所带来的。对于这种实现的细节,我们会在接下来的环节中详细讨论。

    2.2. 堆栈平衡

    堆栈平衡这个概念指的是函数调完成后,要返还所有使用过的栈空间。这种说法可能有点抽象,我们可以举一个简单的例子来类比:
    我们都知道函数的临时变量存放在栈中。那我们来看下面的代码,它是一个很简单的函数,用来交换传入的2个参数的值:

    void __stdcall swap(int& a,int& b)
    {
      int c = a;
      a = b;
      b = c;
    }
    

    我们可以看到,在这个函数中使用了一个临时变量int c;这个变量分配在栈中,我们可以简单的理解为,在声明临时变量c后,我们就向当前的程序栈中压入了一个int值:

    int c = a; <==> push(a);   //简单粗暴,临时变量的声明理解为简单地向栈中push一个值。
    

    那现在这个函数swap调用结束了,我们是否需要退栈,把之前临时变量c使用的栈空间返还回去?需要吗?不需要吗?
    我们假设不需要,当我们频繁调用swap的时候,会发生什么?每次调用,程序栈都在生长。直到栈满,我们就会收到stack overflow错误,程序挂掉了。
    所以为了避免这种乌龙的事情发生,我们需要在函数调用结束后,退栈,把堆栈还原到函数调用前的状态,这些被pop掉的临时变量,自然也就失效了,这也解释了我们一直以来关于临时变量仅在当前函数内有效的认知。其实堆栈平衡这个概念本身比这种粗浅的理解要复杂的多,还应包括压栈参数的平衡,暂时我们可以简单地这样理解,后面再做详细说明。

    2.3. 函数的参数传递和调用约定

    函数的参数传递是一个参数压栈的过程。函数的所有参数,都会依次被push到栈中。那调用约定有是什么呢?
    C和C++程序员应该对所谓的调用约定有一定的印象,就像下面这种代码:

    void __stdcall add(int a,int b);
    

    函数声明中的__stdcall就是关于调用约定的声明。其中标准C函数的默认调用约定是__stdcall,C++全局函数和静态成员函数的默认调用约定是__cdecl,类的成员函数的调用约定是__thiscall。剩下的还有__fastcall,__naked等。
    为什么要用所谓的调用约定?调用约定其实是一种约定方式,它指明了函数调用中的参数传递方式和堆栈平衡方式。

    2.3.1 参数传递方式

    还是之前那个例子,swap函数有2个参数,int a,int b。这两个参数,入栈的顺序谁先谁后?
    其实是从左到右入栈还是从右到左入栈都可以,只要函数调用者和函数内部使用相同的顺序存取参数即可。在上述的所有调用约定中,参数总是从右到左压栈,也就是最后一个参数先入栈。我们可以使用一份伪代码描述这个过程

    push b;      //先压入参数b
    push a;      //再压入参数a
    call swap;  //调用swap函数
    

    其实从这里我们就可以理解为什么在函数内部,不能改变函数外部参数的值:因为函数内部访问到的参数其实是压入栈的变量值,对它的修改只是修改了栈中的"副本"。指针和引用参数才能真正地改变外部变量的值。

    2.3.2 堆栈平衡方式

    因为函数调用过程中,参数需要压栈,所以在函数调用结束后,用于函数调用的压栈参数也需要退栈。那这个工作是交给调用者完成,还是在函数内部自己完成?其实两种都可以。调用者负责平衡堆栈的主要好处是可以实现可变参数(关于可变参数的话题,在此不做过多讨论。如果可能的话,我们可以以一篇单独的文章来讲这个问题),因为在参数可变的情况下,只有调用者才知道具体的压栈参数有几个。
    下面列出了常见调用约定的堆栈平衡方式:

    调用约定堆栈平衡方式
    __stdcall函数自己平衡
    __cdecl调用者负责平衡
    __thiscall调用者负责平衡
    __fastcall调用者负责平衡
    __naked编译器不负责平衡,由编写者自己负责

    2.4. 栈帧的概念:从esp和ebp说起

    为什么我们需要ebp和esp2个寄存器来访问栈?这种观念其实来自于函数的层级调用:函数A调用函数B,函数B调用函数C,函数C调用函数D…
    这种调用可能会涉及非常多的层次。编译器需要保证在这种复杂的嵌套调用中,能够正确地处理每个函数调用的堆栈平衡。所以我们引入了2个寄存器:

    1. ebp指向了本次函数调用开始时的栈顶指针,它也是本次函数调用时的“栈底”(这里的意思是,在一次函数调用中,ebp向下是函数的临时变量使用的空间)。在函数调用开始时,我们会使用把当前的esp保存在ebp中。
    mov ebp,esp 
    
    1. esp,它指向当前的栈顶,它是动态变化的,随着我们申请更多的临时变量,esp值不断减小(正如前文所说,栈是向下生长的)。函数调用结束,我们使用来还原之前保存的esp。
    mov esp,ebp
    

    在函数调用过程中,ebp和esp之间的空间被称为本次函数调用的“栈帧”。函数调用结束后,处于栈帧之前的所有内容都是本次函数调用过程中分配的临时变量,都需要被“返还”。这样在概念上,给了函数调用一个更明显的分界。下图是一个程序运行的某一时刻的栈帧图:

    Paste_Image.png

    3. 汇编中关于“函数调用”的实现

    上面铺陈了很多的汇编层面的概念后,我们终于可以切回到我们本次的主题:函数调用。
    函数调用其实可以看做4个过程,也就是本篇标题:

    1. 压栈: 函数参数压栈,返回地址压栈
    2. 跳转: 跳转到函数所在代码处执行
    3. 执行: 执行函数代码
    4. 返回: 平衡堆栈,找出之前的返回地址,跳转回之前的调用点之后,完成函数调用

    3.1 call指令 压栈和跳转

    下面我们看一下函数调用指令

    0x210000 call swap;
    0x210005 mov ecx,eax; 
    

    我们可以把它理解为2个指令:

    push 0x210005;
    jmp swap;
    

    也就是,首先把call指令的下一条指令地址作为本次函数调用的返回地址压栈,然后使用jmp指令修改指令指针寄存器EIP,使cpu执行swap函数的指令代码。

    3.2 ret指令 返回

    汇编中有ret相关的指令,它表示取出当前栈顶值,作为返回地址,并将指令指针寄存器EIP修改为该值,实现函数返回。
    下面给出一组示意图来演示函数的返回过程:

    1. 当前EIP的值为0x210004,指向指令ret 4,程序需要返回

    Paste_Image.png

    1. 执行ret指令,将当前esp指向的堆栈值当做返回地址,设置eip跳转到此处并弹出该值

    Paste_Image.png

    经过这两步,函数就返回到了调用处。

    4. 从实际汇编代码看函数调用

    4.1 程序源码和运行结果

    源码:

    main.cpp
    
    #include <stdio.h>
     
    void __stdcall swap(int& a, int& b);
     
    int main(int argc, char* argv)
    {
        int a = 1, b = 2;
        printf("before swap: a = %d, b = %d\r\n", a, b);
        swap(a, b);
        printf("after swap: a = %d, b = %d\r\n", a, b);
    }
     
     
    void __stdcall swap(int& a, int& b)
    {
        int c = a;
        a = b;
        b = c;
    }
    

    程序运行结果:

    Paste_Image.png

    4.2 反汇编

    Paste_Image.png

    Paste_Image.png

    可以看到,在函数调用前,函数参数已被压栈,此时:
    EBP = 00AFFCAC
    ESP = 00AFFBBC
    EIP = 00BF1853

    我们按F11,进入函数内部,此时:

    Paste_Image.png

    其实就是call swap指令的下一条指令地址,它就是本次函数调用的返回地址。

    Paste_Image.png

    下面是一个swap函数的详细注释:

    Paste_Image.png

    当程序运行到ret 8时

    Paste_Image.png

    执行返回后:

    Paste_Image.png

    在返回前,ESP = 00AFFBB8,返回后 ESP = 00AFFBC4
    0x00AFFBC4 - 0x00AFFBB8 = 0xC
    这里的数值是字节数,而我们知道,int是4字节长度。所以0xC/4 = 3
    正好是2个压栈参数+一个返回地址。

    4.3 调用堆栈

    调试程序的时候,我们经常关注的一个点就是VisualStudio显示给我们的“调用堆栈”功能,这次让我们来仔细看一下它:
    我们重新执行一次程序,这次我们关注一下vs显示的调用堆栈,如下图

    Paste_Image.png

    第一行是当前指令地址
    第二行是外层调用者,我们双击它,跳转到如下地址:

    Paste_Image.png

    也许这也是为什么这个功能被叫做“调用堆栈”的原因:它正是通过对程序栈的分析实现的。

    转于:https://www.jianshu.com/p/594357dff57e

    展开全文
  • 关于二维数组的函数调用

    千次阅读 2019-09-22 12:53:44
    void change(int a[][3],int b[][2]) { for(int i=0;i<2;i++) { for(int j=0;...解释:因为任何数组传入函数都是传入的数组的首地址,所以不能写成(a[][])形式,数组的传入方式类似于指针,...
  • 函数调用过程

    万次阅读 多人点赞 2018-01-31 09:21:19
    今天突然看到有人私信我说一直没写函数调用过程(栈帧的形成和销毁过程)这篇博文,赶紧补上。 刚看的栈帧内容时,我很迷惑,我觉得栈帧创建和销毁很麻烦,几句话根本说不完,而且我好像描述不清楚他的过程,所以...
  • 函数调用

    千次阅读 2019-01-04 22:32:41
    函数调用 子函数 定义:能被其他程序调用,在实现某种功能后能自动返回到调用程序去的程序。其最后一条指令一定是返回指令,故能保证重新返回到调用它的程序中去。也可调用其他子程序,甚至可自身调用(如递归...
  • 这篇文章为各位带来的内容是Matlab中一个函数调用另外一个函数相关的,对此感兴趣的用户可以去下文看看Matlab中一个函数调用另外一个函数的操作教程。Matlab中一个函数调用另外一个函数的操作方法方法一:主函数调用...
  • 通过函数的入口地址调用函数

    万次阅读 2018-06-12 09:30:27
    例程:int i; //定义一个测试变量void test() //定义一个函数{i = 6; //给测试变量赋初值}int main(){int addr; //定义一个保存地址的变量addr = (int)... //根据函数入口地址调用test函数//((void(*)(void))addr)(...
  • 如下为示例说明:1、python函数的应用一般需要:先定义、后调用: 2、如果函数定义在调用之后,执行将报错: 3、函数中调用函数不受此限制: 4、但是语句对函数调用,必须在函数调用之后,包括直接调用的函数调用的...
  • 静态函数调用

    千次阅读 2020-01-09 15:48:09
    静态函数 #include <iostream.h> class Point { public: void output()//非静态成员函数 ...调用1:非静态成员函数和非静态成员属于对象的方法和数据,也就是先产生类的对象,然后通过类的对象去引用。 voi...
  • 【C/C++】获取函数地址调用

    千次阅读 2018-07-19 16:39:02
    函数地址函数指针
  • VUE中函数调用函数

    千次阅读 2020-04-17 16:55:08
    VUE中函数调用函数VUE中函数调用函数 VUE中函数调用函数 有时候,需要在一个函数中,调用另外一个函数,采用这种方法 this.$options.methods.onQuery(‘queryForm’); ...
  • 在matlab里.m文件分执行文件和函数文件 在c++中执行文件指:main函数 函数文件:其他所有需要用到的函数 在c++中,函数文件名没有特殊讲究,将文件添加到工程目录便能使用 对函数的要求有三点 函数的完整文件 输入...
  • 函数调用堆栈的过程

    万次阅读 多人点赞 2018-05-31 11:46:43
    本篇来分析函数调用的过程:通过下面一个简单的例子来进入话题:#include&lt;stdio.h&gt; int sum(int a,int b) { int tmp=0; tmp=a+b; return tmp; } int main() { int a=10; int b=20; int ret=0; ...
  • C语言函数调用时候内存中栈的动态变化详细分析

    千次阅读 多人点赞 2019-05-02 00:23:41
    先了解如下几点知识和过程: ...这些指令代码是存放在内存中进程的代码段,同一个函数内的指令代码是按照地址顺序存储的(编译器决定的)(也就是说只要指令地址+1就可以自动得到下一条指令的地址...
  • 函数本地调用与云函数调用

    千次阅读 2020-08-09 15:29:28
    现在我们来讲一下常用的云函数调用。 云函数基本调用方法 根据官网给出的本地调用云函数的基本示例,本地调用云函数采用wx.cloud.callFunctionAPI,通过wx.cloud.callFunction获取的结果是一个Promise对象,因此官方...
  • 函数调用的执行过程

    千次阅读 2020-10-28 11:26:15
    函数,英文名称function,function的更贴切的意思是“功能”或者是说“作用”,但是在中国为什么将function翻译成函数,我非常不理解,也许函数这个名称更加“高大上”的缘故吧! 一、函数的由来 一个程序经常会通过...
  • 程序是如何执行的(三)函数调用

    千次阅读 2017-11-11 10:17:19
     函数中出现的变量可以分为局部变量和全局变量,在函数内部定义的变量(没有global语句)就是局部变量,只有在函数内部才能够使用它们。在函数外定义的变量就是全局变量 全局变量的作用是增加了函数间数据联系的...
  • x86_64汇编之四:函数调用、调用约定

    千次阅读 多人点赞 2020-07-09 18:26:09
    栈一般从高地址往低地址生长,每个函数都在栈空间中对应一个栈帧。关于栈帧有两个重要的指针——栈基址指针BP和栈顶指针SP。其中,除了在函数的开头和结尾(后面会讲到),BP指针一般是固定不变的,通常以它为基准来...
  • 我们一般将字符串、列表等变量作为参数进行函数调用。但函数本身也是一个对象,所以我们也可以将函数作为参数传入另外一个函数中并进行调用。 本关的目标是让读者了解并掌握函数作为参数传入另外一个函数中并进行...
  • python函数调用的五种方式

    千次阅读 2021-05-17 14:12:15
    第一种:参数按顺序从第一个参数往后排#标准调用 # -*- coding: UTF-8 -*- def normal_invoke(x, y): print "--normal_invoke:--" print "x is %d" %x print "y is %d" %y #标准调用 normal_invoke(1, 2) ...
  • 以数组作为函数参数的函数调用

    万次阅读 2018-11-28 15:04:44
    因此数组元素可以用作函数实参,但是不能用作函数形参(因为在函数调用时临时分配存储单元,不可能为一个数组单独分配存储单元数)数组名可以做实参和形参,但此时传递的是数组第一个元素的地址; 用一个一维数组...
  • c语言函数调用及应用举例

    万次阅读 多人点赞 2019-04-20 16:27:29
    分为:I/O函数,字符串,字符处理函数,数学函数,接口函数,时间转换和操作函数,动态地址分配函数,目录函数,过程控制函数,字符屏幕和图形功能函数。 这些库函数在不同的头文件中声明。比如: math.h头文件中...
  • 自定义函数及函数调用

    千次阅读 多人点赞 2019-05-10 12:17:10
    1、自定义函数声明、函数调用、函数实现概念混淆 2、形参和实参混淆; 3、自定义函数参数的传值方式混淆;传来传去都不知道传的到底是什么? 接下来,我就重点围绕道友们常见的问题,说一说: 1、自定义函数声明...
  • 用处 要想了解大型工程的运行原理和过程,分析其函数调用关系是一个很有用的方法。比起自己一个个搜索、记录,使用工具效率会更高。Source Insight 就有一个显示函数调用关系的功能,非常好用。 PS:之前我都是手动...
  • 静态成员函数调用方式--收藏帖子

    千次阅读 2018-12-13 17:14:56
    静态成员函数调用方式 A:: s_fun();//不需要额外传递一个参数,作为this 指针;因为静态函数,属于类,不属于具体对象。非静态成员函数调用方式 。非静态成员函数,属于对象,需要隐式传递 this 指针作为参数...
  • python函数调用另外的一个函数

    千次阅读 2020-08-20 17:49:14
    1、使用场景:一个发送短信的函数,一个登陆的函数,登陆前需要调用发送短信 def send_message(ip): """1、发送短信""" url = "http://" + ip + ".123.com/api/sms/send" print(url) params = { "mobile": ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,922,278
精华内容 1,968,911
关键字:

地址函数调用