按照C编译器的约定调用函数时压栈的顺序是从右向左,并且返回值是保存在eax寄存器当中。这个命题本该是成立的,下面用一个小程序来反汇编观察执行过程:

#include<stdio.h>

int add(int x, int y){
return x+y;
} int main(){
int eax=0;
int z =0;
int x =6;
int y =5; z=add(x,y); __asm__(
"movl %%eax, %0"
:"+b"(eax)
:"m"(x)
); printf("z is %d\n", z);
printf("eax is %d\n", eax);
return 0;
}

代码解释一下,asm的代码中movl %%eax, %0的意思是把寄存器eax的值赋值给咱们程序的eax变量当中。但为什么执行结果却是:

z is 11
eax is 0

理论上应该是x和y相加返回的结果才对啊。反汇编一下此exe程序:

上面是main函数,看下图

esp自减了20h,说明开辟了20h也就是32字节的栈空间,再看下图:

[esp+1ch]对应的是程序中的变量eax,也就是把eax压在了esp+28处,此变量是int型4个字节,所以刚好对应的是栈底元素;[esp+18h]对应的是z也就是esp+24处,[esp+10h]对应的是x也就是esp+16处,[esp+14h]对应的是y。再看下图

先把[esp+10h]的值也就是x的值赋给eax,再把[esp+14h]的值也就是y的值赋给edx,再分别把它们赋给[esp+4]和[esp]处,注意这里没用push指令压栈,但原理却是一样,因为用的是栈指针esp,还需要注意的是因为不是使用push指令,所以不是说谁先执行谁就先压栈,而是观察esp指向的位置来确定压栈的先后顺序,因为[esp]指向的是栈顶元素。所以这里就解释了先把y压栈,再把x压栈,确实是从右向左压栈

接下来再看add调用:

注意上面call add指令会先把eip压栈,相当于esp=esp-4,并且这里还执行了push ebp指令,所以esp又自减了4,那么x的值就不再是入栈时候的[esp]了,而是[esp+8],所以y的值也不再是[esp+4]而是[esp+12],所以这里出栈的时候也不是看执行的先后顺序,而是x本身就处于栈顶,相加后结果保存在eax里。然后再回到main函数

调用完add后把eax的值赋值给了z,这就说明函数的返回值确实是保存在eax中。但为什么打印出来的eax却是0呢。

接着往下看,

首先把程序中eax变量的值赋给了eax寄存器,那当然就是0了。所以现在深入理解了C语言嵌入汇编的执行过程,就算指定了"+b"赋给ebx寄存器,但编译器还是会先把变量的值赋给eax寄存器,再赋值给ebx,返回也是一样的原理,如下图:

关于C语言函数调用压栈和返回值问题的疑惑的更多相关文章

  1. Swift2.0语言教程之函数的返回值与函数类型

    Swift2.0语言教程之函数的返回值与函数类型 Swift2.0中函数的返回值 根据是否具有返回值,函数可以分为无返回值函数和有返回值函数.以下将会对这两种函数类型进行讲解. Swift2.0中具有 ...

  2. C语言函数调用及栈帧结构

    source:http://blog.csdn.net/qq_29403077/article/details/53205010 一.地址空间与物理内存 (1)地址空间与物理内存是两个完全不同的概念, ...

  3. C语言 realloc为什么要有返回值,realloc返回值具体解释/(解决随意长度字符串输入问题)。

    在C语言操作中会用到大量的内存操作,当中非经常常使用的一个是realloc(). 由字面意思能够知道,该函数的作用是用于又一次分配内存. 使用方式例如以下: NewPtr=(数据类型*)realloc ...

  4. 013_go语言中的函数多返回值

    代码演示 package main import "fmt" func vals() (int, int) { return 3, 7 } func main() { a, b : ...

  5. C语言中赋值表达式的返回值是什么?

    我们或多或少都有过,或者见过将赋值表达式参与运算的情况.这通常会伴随着一些意想不到的问题.今天我就见到了一段奇怪的代码: #include<stdio.h> int main() { ; ...

  6. r语言 function 指定多个返回值

    # Goals: To write functions # To write functions that send back multiple objects. # FIRST LEARN ABOU ...

  7. 汇编中call printf参数压栈时错误理解

    EAX, ECX,EDX,EBX均可以32bit,16bit,8bit访问,如下所示: <-------------------EAX------------------------>|& ...

  8. C++获取Lua全局变量和执行Lua多参数多返回值函数

    C++代码: // LuaAndC.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> #i ...

  9. [转]WinExec、ShellExecute和CreateProcess及返回值判断方式

    [转]WinExec.ShellExecute和CreateProcess及返回值判断方式 http://www.cnblogs.com/ziwuge/archive/2012/03/12/23924 ...

随机推荐

  1. datatable去重

    两种方法1 数据库直接去除重复select distinct * from 表名去除了重复行distinct 2 对 DataTable直接进行操作DataTable dt=db.GetDt(&quo ...

  2. Windows phone重写返回键

    protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e) {//需要设置这个属性 e.Cancel ...

  3. Vue.js实现checkbox的全选和反选

    小颖之前写的代码存在一个bug,就是当你选择全选的时候去掉后面的一个选项,再点全选结果就是反的了.很感谢博客园的朋友帮我改了这个问题嘻嘻,下面一起来看看具体是怎么实现的吧. 1.html <te ...

  4. ASP.NET WebAPi(selfhost)之文件同步或异步上传

    前言 前面我们讲过利用AngularJs上传到WebAPi中进行处理,同时我们在MVC系列中讲过文件上传,本文结合MVC+WebAPi来进行文件的同步或者异步上传,顺便回顾下css和js,MVC作为客 ...

  5. 解决在配置Oracle数据库连接错误问题

    项目开发使用的Oracle,但是使用了不同的版本的Oracle数据库.然后自己在电脑上安装了2个连接客户端,版本分别是: 10.2.0 12.1.0 用Toad for Oracle 9.0的时候,我 ...

  6. Connect(); // 2015 简要整理

    去年 Connect(); 2014 Visual Studio Contact(); 直播笔记 对于我个人来说,今年 Connect(); 的三个重要发布: ASP.NET 5 RC1 Entity ...

  7. 记录一则RMAN备份策略修正案例

    背景:在给某客户处理问题时,发现客户数据库的备份空间即将用尽,进一步查看发现是用户数据库的当前RMAN备份策略存在潜在问题,需要修改备份策略. 环境:SunOS 5.10 + Oracle 11.2. ...

  8. Cesium原理篇:4Web Workers剖析(2)

    What's the WebWorkers? 2008 年 W3C 制定出第一个 HTML5 草案中提出了工作线程(Web Worker)的概念,并且规范出 Web Worker 的三大主要特征:能够 ...

  9. 解析Exception和C#处理Exception的常用方法总结

    在.NET中,异常是指成员没有完成它的名称宣称可以完成的行动.在异常的机制中,异常和某件事情的发生频率无关. 异常处理四要素包括:一个表示异常详细信息的类类型:一个向调用者引发异常类实例的成员:调用者 ...

  10. 函数----Beginning Visual C#

    span.kw { color: #007020; font-weight: bold; } code > span.dt { color: #902000; } code > span. ...