按照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. Hibernate注解

    前言: 最近正在学习Hibernate通过注解(annotation)来管理映射关系,以前都是通过XML映射文件.下面拿个小例子说一下. 数据库物理模型: 数据库的描述: 一篇博客随笔可以分到不同的类 ...

  2. Android音视频之MediaPlayer音视频播放

    前言: 昨天总结了视频录制,今天来学习一下视频的播放,Android的视频播放主要采用MediaPlayer类. MediaPlayer介绍 MediaPlayer类可用于控制音频/视频文件或流的播放 ...

  3. Util应用程序框架公共操作类(七):Lambda表达式公共操作类

    前一篇扩展了两个常用验证方法,本文将封装两个Lambda表达式操作,用来为下一篇的查询扩展服务. Lambda表达式是一种简洁的匿名函数语法,可以用它将方法作为委托参数传递.在Linq中,大量使用La ...

  4. MVVM 开发的几种模式讨论(WPF)

    在WPF系(包括SL,WP或者Win8)应用开发中,MVVM是个老生常谈的问题.初学者可能不会有感觉,但当你写一个核心逻辑能在各种平台上无缝移植,而只需改改UI的时候,那种快感是无法用语言来形容的. ...

  5. 【重磅开源】Hawk-数据抓取工具:简明教程

    Hawk-数据抓取工具:简明教程 标签(空格分隔): Hawk Hawk: Advanced Crawler& ETL tool written in C#/WPF 1.软件介绍 HAWK是一 ...

  6. Linux笔记之——Linux关机命令详解(转)

    原文连接:http://www.jb51.net/os/RedHat/1334.html 在linux下一些常用的关机/重启命令有shutdown.halt.reboot.及init,它们都可以达到重 ...

  7. Ubuntu杂记——Ubuntu下用虚拟机共享上网

    由于最近把自己电脑环境换成了Ubuntu,但学校的网络是电信的闪讯,大学里用过的人都知道这货有多坑,而且没有Linux客户端,上网都是问题,怪不得国内用Linux的人那么少,特别是高校的学生(让我瞎逼 ...

  8. kafka源码分析之二客户端分析

    客户端由两种:生产者和消费者 1. 生产者 先看一下生产者的构造方法: private KafkaProducer(ProducerConfig config, Serializer<K> ...

  9. 开启了HA的XenServer如何关闭虚拟机?

    可开启了HA很方便,在主机自己坏掉的情况下其中的虚拟机能自己飘到活的机器上并被运行起来,不过如果手动的需要关闭虚拟机的话在这情况下,该虚拟机会自己"复活"即便我们选的是关机. 此时 ...

  10. Hammer.js分析(三)——input.js

    input.js是所有input文件夹中类的父类,浏览器事件绑定.初始化特定的input类.各种参数计算函数. Input父类和其子类就是在做绑定事件,各种参数计算.整合.设置等返回自定义事件对象,交 ...