精华内容
下载资源
问答
  • C语言函数调用

    千次阅读 多人点赞 2019-04-30 14:50:25
    C函数调用 调用格式: 函数名(实参表) 并且应该避免写出与实际参数计算次序有关的调用。 函数调用可以出现在以下两种情况中: 无返回值的函数通常作为表达式语句,即直接在函数调用的后面加一个分号; 有返回值的...

    C函数调用

    调用格式:
    函数名(实参表)
    并且应该避免写出与实际参数计算次序有关的调用。
    函数调用可以出现在以下两种情况中:

    • 无返回值的函数通常作为表达式语句,即直接在函数调用的后面加一个分号;
    • 有返回值的函数可以作为任何表达式的一部分,如算术表达式、逻辑表达式。

    每一段函数的开始前都应该有一段注释,方便其他人查看

    函数调用过程

    • 在main函数或调用函数中计算每一个实际参数的值。
    • 将实际参数作为形参的初值。在调用的过程中,如果形式参数和实际参数的类型不一样的时候,自动完成类型转换,将实际参数的类型转换成为形式参数的类型。
    • 依次执行被调函数的函数体的每个语句,直到遇到return语句或者函数体结束。
    • 如果return后面有表达式,计算表达式的值,,如果表达式的值与函数的类型不一致,完成类型转换。
    • 回到调用函数。如果有返回值,在函数调用的地方用return后面的返回值代替。
    • 继续调用函数的执行。
    展开全文
  • C语言函数调用的底层机制C语言函数调用的底层机制C语言函数调用的底层机制C语言函数调用的底层机制C语言函数调用的底层机制C语言函数调用的底层机制
  • C语言函数调用过程

    千次阅读 2016-07-08 19:06:02
    [读书笔记]C语言函数调用过程 c   legendmohe 2015年07月12日发布 推荐 0 推荐 收藏 0 收藏,392 浏览 *** 本文是《老码识途》第一章的读书笔记 *** 函数调用 例子...

    [读书笔记]C语言函数调用过程

     

    *** 本文是《老码识途》第一章的读书笔记 ***

    函数调用

    例子代码如下所示:

    int Add(int x, int y) {
        int sum;
        sum = x + y;
        return sum;
    }
    
    void main() {
        int z;
        z = Add(1, 2);
        printf("z=%d\n", z);
    }
    

    下面分析一下 Add函数的调用过程。

    首先断点在z = Add(1, 2);处, 反汇编如下所示:

        int z;
        z = Add(1, 2);
    002C141E 6A 02                push        2  
    002C1420 6A 01                push        1  
    002C1422 E8 60 FC FF FF       call        002C1087  
    002C1427 83 C4 08             add         esp,8  
    002C142A 89 45 F8             mov         dword ptr [ebp-8],eax
    

    首先压入参数1和2:

    002C141E 6A 02                push        2  
    002C1420 6A 01                push        1  
    

    通过观察ESP可以看到参数从右到左依次入栈,ESP往低内存方向移动8字节:

    ESP=0025FCCC
    ...
    0x0025FCAA  00 00 78 4c 33 00 bc fc 25 00 a9 fe aa 0f 78 4c 33 00 c8 fc 25 00 3d 5a b2 0f *** 01 00 00 00 02 00 00 00 ***
    0x0025FCCC  00 00 00 00
    

    然后执行:

    002C1422 E8 60 FC FF FF       call        002C1087  
    

    call指令执行时,首先压入call指令的返回地址,即add esp,8这一句的地址002C1427:

    0x0025FCAA  00 00 78 4c 33 00 bc fc 25 00 a9 fe aa 0f 78 4c 33 00 c8 fc 25 00 *** 27 14 2c 00 *** 01 00 00 00 02 00 00 00
    

    然后跳转到02C1087。02C1087处为jmp语句,跳转到Add函数入口地址002C13C0:

    int Add(int x, int y) {
    002C13C0 55                   push        ebp  
    002C13C1 8B EC                mov         ebp,esp  
    002C13C3 81 EC CC 00 00 00    sub         esp,0CCh  
    002C13C9 53                   push        ebx  
    002C13CA 56                   push        esi  
    002C13CB 57                   push        edi  
    002C13CC 8D BD 34 FF FF FF    lea         edi,[ebp+FFFFFF34h]  
    002C13D2 B9 33 00 00 00       mov         ecx,33h  
    002C13D7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
    002C13DC F3 AB                rep stos    dword ptr es:[edi]  
        int sum;
        sum = x + y;
    002C13DE 8B 45 08             mov         eax,dword ptr [ebp+8]  
    002C13E1 03 45 0C             add         eax,dword ptr [ebp+0Ch]  
    002C13E4 89 45 F8             mov         dword ptr [ebp-8],eax  
        return sum;
    002C13E7 8B 45 F8             mov         eax,dword ptr [ebp-8]  
    }
    002C13EA 5F                   pop         edi  
    002C13EB 5E                   pop         esi  
    002C13EC 5B                   pop         ebx  
    002C13ED 8B E5                mov         esp,ebp  
    002C13EF 5D                   pop         ebp  
    002C13F0 C3                   ret  
    

    获取参数

    目前为止,栈上的情况如下图所示,从上往下内存地址从高到低:

              +----------------+
              |       2        |
              +----------------+
              |       1        |
              +----------------+
    ESP       | return address |
     +------> +----------------+
    

    此时参数可由ESP + 4,ESP + 8获得。但是由于程序执行时ESP会变化,为了方便定位栈上的数据,引入EBP(Extended Base Pointer,扩展基址指针寄存器),保存进入函数时ESP的值。
    由于函数可以嵌套调用,所以在进入函数时必须将EBP的旧值保存起来,以防覆盖EBP导致函数返回后无法恢复EBP。这里通过将EBP压入栈来保存旧值。如:

    002C13C0 55                   push        ebp  
    002C13C1 8B EC                mov         ebp,esp  
    ...
    002C13EF 5D                   pop         ebp  
    

    所以在函数开头有如下代码:

    int Add(int x, int y) {
    002C13C0 55                   push        ebp  
    002C13C1 8B EC                mov         ebp,esp
    

    此时栈上的内存布局如下图所示:

              +----------------+
              |       2        |
              +----------------+
              |       1        |
              +----------------+
              | return address |
              +----------------+
    ESP       |     ebp        |
     +------> +----------------+
    

    取出1,2参数的代码如下所示:

        int sum;
        sum = x + y;
    002C13DE 8B 45 08             mov         eax,dword ptr [ebp+8]  
    002C13E1 03 45 0C             add         eax,dword ptr [ebp+0Ch]
    

    其中ebp+8取出参数1,ebp+0Ch取出参数2(0C为十进制的12),然后计算结果放在EAX中。

    初始化堆栈和分配局部变量

    接下来有如下代码段,将esp下移0CC,然后push ebx,esi,edi这三个寄存器:

    002C13C3 81 EC CC 00 00 00    sub         esp,0CCh  // 1
    002C13C9 53                   push        ebx  
    002C13CA 56                   push        esi  
    002C13CB 57                   push        edi
    

    其中语句1是为了给局部变量分配足够大的栈空间,然后再保存三个寄存器的值。局部变量利用ebp定位,存于ebp和OCCh之间。

    ebx,esi,edi的作用如下所示来源

    寄存器%ebx、%esi和%edi为被调函数保存寄存器(callee-saved registers),即被调函数在覆盖这些寄存器的值时,必须先将寄存器原值压入栈中保存起来,并在函数返回前从栈中恢复其原值,因为主调函数可能也在使用这些寄存器。

    然后运行:

    002C13CC 8D BD 34 FF FF FF    lea         edi,[ebp+FFFFFF34h]  
    002C13D2 B9 33 00 00 00       mov         ecx,33h  
    002C13D7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
    002C13DC F3 AB                rep stos    dword ptr es:[edi]  
    

    首先,通过lea指令,将ebp+FFFFFF34h地址(栈帧的底部)写入edi。然后设置ecx和eax,最后运行rep stos语句。

    rep stos dword ptr es:[edi]语句的意思是:将栈上从ebp+FFFFFF34h开始的位置向高地址方向的内存赋值eax(0xCCCCCCCC),次数重复ecx(0x33, 51)次。每运行一次edi的值会增加。注意0xCCCCCCCC代表着未被初始化(int3中断)。这样做的原因是防止分配好的局部变量空间中的代码被意外执行。

    局部变量

    示例代码中,x+y的结果保存在局部变量sum中,由如下代码可知,sum分配在栈上。

        int sum;
        sum = x + y;
    002C13DE 8B 45 08             mov         eax,dword ptr [ebp+8]  
    002C13E1 03 45 0C             add         eax,dword ptr [ebp+0Ch]
    002C13E4 89 45 F8             mov         dword ptr [ebp-8],eax   <<==== 结果保存在局部变量sum中
    

    目前栈的分配情况如下所示:

              +----------------+                
              |       2        |                
              +----------------+                
              |       1        |                
              +----------------+                
              | return address |                
              +----------------+                
              |      ebp       |                
              +----------------+                
              |       ?        |                
              +----------------+               
    ESP       |      sum       |                
     +------> +----------------+                
    

    “?”处的4字节是编译器为了防止溢出攻击而设置的。

    返回值

    函数返回处的代码如下:

    002C13EA 5F                   pop         edi  
    002C13EB 5E                   pop         esi  
    002C13EC 5B                   pop         ebx  
    002C13ED 8B E5                mov         esp,ebp  
    002C13EF 5D                   pop         ebp  
    002C13F0 C3                   ret  
    

    函数返回时需要考虑两件事情:恢复栈和保存返回值。

    恢复栈

    首先,通过pop栈恢复edi,esi和ebx的值,然后将esp恢复到ebp处,然后pop ebp,将ebp恢复旧值。此时esp指向return address:

              +----------------+                
              |       2        |                
              +----------------+                
              |       1        |                
              +----------------+                
     ESP      | return address |                
      +------>+----------------+                
              |      ebp       |                
              +----------------+                
              |       ?        |                
              +----------------+               
              |      sum       |                
              +----------------+  
    

    接下来运行ret指令。ret指令会将栈顶保存的地址压入指令寄存器EIP,相当于pop eip。运行后EIP和ESP都会有变化。

    然后程序跳转到return address处,如下所示:

    <button href="javascript:void(0);" _xhe_href="javascript:void(0);" class="copyCode btn btn-xs" data-clipboard-text="" int="" z;"="" data-toggle="tooltip" data-placement="bottom" title="" style="color: rgb(255, 255, 255); font-family: inherit; font-size: 12px; font-style: inherit; font-variant: inherit; line-height: 1.5; margin: 0px 0px 0px 5px; overflow: visible; cursor: pointer; vertical-align: middle; border: 1px solid transparent; white-space: nowrap; padding-right: 5px; padding-left: 5px; border-radius: 3px; -webkit-user-select: none; box-shadow: rgba(0, 0, 0, 0.0980392) 0px 1px 2px; background-image: none; background-color: rgba(0, 0, 0, 0.74902);">复制
    int z; z = Add(1, 2); 002C141E 6A 02 push 2 002C1420 6A 01 push 1 002C1422 E8 60 FC FF FF call 002C1087 002C1427 83 C4 08 add esp,8 // ret跳转到此处 002C142A 89 45 F8 mov dword ptr [ebp-8],eax

    其中add esp,8语句的目的是将1,2参数出栈,将栈恢复到函数调用之前的状态。接下来便可以从eax中取出返回值:

    002C142A 89 45 F8             mov         dword ptr [ebp-8],eax
    

    由于ebp已被恢复,故其中ebp-8即为临时变量z的地址

    展开全文
  • C语言函数调用三种方式:传值调用,引用调用和传地址调用

    我想,你只要看了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

    展开全文
  • c语言函数调用过程-栈帧详解

    千次阅读 2018-03-10 16:47:41
    c语言函数调用过程-栈帧详解 c语言函数调用过程-栈帧详解 通过栈帧结构修改函数返回时的跳转地址 使非正常跳转的函数回来 手动调整main栈帧结构 测试环境,vs2017,win10 用一个测试代码来看 #...

    c语言函数调用过程-栈帧详解

    测试环境,vs2017,win10

    用一个测试代码来看

    #include <stdio.h>
    
    int fun(int a, int b)
    {
        int z = a + b;
        return z;
    
    }
    int main()
    {
        int a = 0xaaaaaaaa;
        int b = 0x11111111;
        int c = fun(a, b);
        printf("%d", c);
    
    }

    F10单步调试到fun函数调用这步,查看反汇编代码是这个样的

        int a = 0xaaaaaaaa;
    00DA180E  mov         dword ptr [a],0AAAAAAAAh  
        int b = 0x11111111;
    00DA1815  mov         dword ptr [b],11111111h  
        int c = fun(a, b);
    00DA181C  mov         eax,dword ptr [b]  
    00DA181F  push        eax  
    00DA1820  mov         ecx,dword ptr [a]  
    00DA1823  push        ecx  
    00DA1824  call        _fun (0DA113Bh)  
    00DA1829  add         esp,8  
    00DA182C  mov         dword ptr [c],eax  
        printf("%d", c);

    首先要明白函数中的non-static变量都是存在于进程的地址空间中的栈区。

    *这段进程的地址空间会在程序运行期间一直存在

    ebp 指向栈底, esp 指向栈顶,eip 存放cpu下一条要读取的指令地址

    9RjaxP.png

    栈区从高地址向下生长,压入新的变量。

    从汇编的前两句push可以看出函数的两个参数b先压入,a后进入栈中,然后调用call指令。

    call 指令有两个作用

    1. 先把call 的下一条指令地址存到内存中(栈)
    2. 将eip的值用函数的地址覆盖(完成了函数跳转)

    9RjcPs.png

    之后进入F11 进入 fun函数的汇编指令

    int fun(int a, int b)
    {
    00DA17B0  push        ebp  
    00DA17B1  mov         ebp,esp  
    00DA17B3  sub         esp,0CCh    
    ...
        return z;
    00DA17D7  mov         eax,dword ptr [z]  
    
    }
    ...
    00DA17DD  mov         esp,ebp  
    00DA17DF  pop         ebp  
    00DA17E0  ret

    这段代码首先压入ebp,这时ebp的值是main函数的栈底,然后ebp指向esp,给esp减去一个常数等于开辟了一块新的空间,这个新的空间就是fun函数的栈帧。

    9RjhrT.png

    当有一个返回值z时,把他保存到eax寄存器里,然后移动ebp,esp,使得回到main函数的栈帧中。调用ret,和call相反,先pop出之前在栈里保存的call的下一条指令地址,然后覆盖eip达到回去的目的。
    9Rjoa4.png

    回去看一下call的下一条命令地址是不是0x00da1829!!!

    下面在内存中确认一下栈中的数据对不对
    9RjIZF.png

    和我们说的栈数据一致。

    通过栈帧结构修改函数返回时的跳转地址

    #include <stdio.h>
    #include <windows.h>
    void fun_b()
    {
        int a;
        printf("fun b");
        Sleep(3000);
    }
    
    int fun_a(int x)
    {
        int c = 0;
        int *p = &x;
        p--;
        *p = fun_b;
        printf("fun a 函数\n");
    }
    int main()
    {
    
        printf("主函数\n");
        fun_a(1);
        system("pause");
    }
    

    第一个参数地址加一就是返回指令的地址,修改为funb函数的地址,达到跳转效果。也可以用c变量地址向上寻找。

    打印结果:

    主函数

    fun a 函数

    fun b

    然后崩溃,因为funb在执行完之后不知道跳转到哪里(因为之前没有用call调用funb,所以少压入一个返回指令地址,esp应该是比funa开始的esp大1的)。

    使非正常跳转的函数回来

    上面的问题就是funb不知道返回地址,那我们可以在进入funa的时候保存一下call的下一条指令地址。

    #include <stdio.h>
    #include <windows.h>
    void * ret = NULL;
    void fun_b()
    {
        int a;
        int *p = &a;
        printf("fun b\n");
        p += 4;                          //在这里因为没有参数,所以用第一个变量地址找返回指令的地址,
                                        //在我的环境下是+4
        *p = (int)ret;
        Sleep(3000);
    }
    
    int fun_a(int x)
    {
        int c = 0;
        int *p = &x;
        p--;
        ret = *p;
        *p = fun_b;
        printf("fun a 函数\n");
    }
    int main()
    {
    
        printf("主函数\n");
        fun_a(1);
        printf("我又回来了!\n");
        system("pause");
    }
    

    打印结果:

    主函数
    fun a 函数
    fun b
    我又回来了!
    请按任意键继续…

    手动调整main栈帧结构

    但是一按回车就又崩了,这个问题是因为不正常函数跳转导致少一次call 压栈,最后多push了一次,导致main函数的栈帧结构不平衡,手动给ESP-4。

    #include <stdio.h>
    #include <windows.h>
    void * ret = NULL;
    void fun_b()
    {
        int a;
        int *p = &a;
        printf("fun b\n");
        p += 4;
        *p = (int)ret;
        Sleep(3000);
    }
    
    int fun_a(int x)
    {
        int c = 0;
        int *p = &x;
        p--;
        ret = *p;
        *p = fun_b;
        printf("fun a 函数\n");
    }
    int main()
    {
    
        printf("主函数\n");
        fun_a(1);
        printf("我又回来了!\n");
        _asm {
            sub esp, 4;             //在这里嵌入汇编代码,ok啦
        }
        system("pause");
    
    }
    

    完美解决~~

    展开全文
  • C语言函数调用及栈帧分析

    千次阅读 2015-11-16 10:00:54
    C语言函数调用及栈帧分析
  • C语言函数调用与中断处理

    千次阅读 2018-08-16 19:50:05
    C语言函数调用就好比,你在一个村庄,这个村庄共有100户人家,你就是CPU,100户人家就是100个函数。 你去串门,就是去执行一个函数,你每次只能串一户人家,进户出户就好比进栈出栈,你平时的任务就是不断的去王五...
  • C语言函数调用栈(一)

    万次阅读 多人点赞 2018-07-19 22:16:25
    程序的执行过程可看作连续的函数调用。当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接call指令)处继续执行。函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(call stack)。编译器使用...
  • C语言函数调用栈浅析

    2014-07-20 13:29:13
    C语言函数调用栈浅析 一直在做较为底层的编程,很早以前就想写一篇关于C语言调用栈相关的文章。正好这一次和一个同事一起做debug tool。 其中要做一个call stack的功能。于是我又重新打开IA32的文档,把C调用栈的...
  • c语言函数调用及应用举例

    万次阅读 多人点赞 2019-04-20 16:27:29
    一般来说,执行源程序就是执行主函数main,其他函数只能被主函数所调用,而其他函数之间也可以相互调用。 1.标准库函数: 分为:I/O函数,字符串,字符处理函数,数学函数,接口函数,时间转换和操作函数,动态...
  • C语言 函数调用栈变化 (VC6.0)
  • 浅谈C语言函数调用与系统调用

    千次阅读 2019-11-12 09:55:50
    很多初学C语言的同学,亦或者开发中很少接触系统底层的同学可能会认为函数调用以及系统调用是一回事。因为在应用程序,两者都被抽象成接口去给应用程序调用。其实函数调用和系统调用还是有区别,我们通过下图先有个...
  • C语言函数调用时的栈帧结构变化  请观察我的这一段简单代码: #include #include int fun(int x,int y) { int *p = &x; p++; printf("Before b value :%x\n",*p); *p = 0xcccccccc; return *p; } int main...
  • c语言 函数调用中实际参数为赋值表达式情况 例 #include<stdio.h> void zm_f(int); int main(void) { int a; a=2; printf(“a=%d\n”,a); zm_f(++a==3?8:1);/或者是将++a与其他值比较/ printf(“now a=%d\n”,...
  • C语言基础知识:C语言函数调用怎么返回两个值

    万次阅读 多人点赞 2018-09-22 11:45:29
    C语言中,函数只能返回一个值,要返回两个值,可以改换思路,通过其它方式做到。 1 建立数组,返回指针。 在要返回的两个值类型相同时,可以用创建数组的方式,将要返回的值存在数组中,并返回数组首地址,这样...
  • c语言函数调用模型

    2018-12-01 14:05:04
    首先分级调用函数 使需要的数据入栈 然后逐级出栈,返回数值,将栈区的内存进行释放    在main函数执行完之前 fa fb 都可以调用main函数在堆栈全局区的内存 fb申请的内存fa main可以使用吗? 1.fb在...
  • 函数调用堆栈过程 调用约定 函数的调用约定很多,常见的包括__stdcall,__cdecl,__fastcall,__thiscall等等。主要的区别在于约束的三个事件,一个是参数传递是从左开始呢还是从右开始,还有就是堆栈清理的清理方是...
  • C语言函数调用机制

    千次阅读 2016-02-25 19:55:01
    本文使用一个简单的C语言的例子探索一下函数的调用机制,函数调用关系如下:int g(int x) { return x + 10; }int f(int x) { return g(x); }int main(void) { return f(10) + 5; }通过编译生成汇编代码分析调用...
  • Python3.x + Graphviz自动化绘制C语言 函数调用关系图、结构体关系图 已使用Cython将Python代码转化为c,可以自行编译安装 1.2. 软件架构 软件架构说明 1.3. 安装教程 sudo -H pip3 in...
  • 数据结构与算法中的顺序表里面经常出现调用方法,有的在里面用&,有的用*,这是怎么区别
  • c语言函数调用原理底层分析

    千次阅读 2019-03-26 22:51:27
    2函数调用机制 2.1按调用约定传递参数 2.1调用约定 调用方(caller)和被调方(callee)需要约定以下内容: 1传参顺序 2传递参数的媒介(是放栈还是寄存器) 3清理参数的责任归属 4返回值的传递媒介(返回值...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,168
精华内容 10,467
关键字:

c语言函数调用

c语言 订阅