关于C语言函数调用压栈和返回值问题的疑惑
按照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语言函数调用压栈和返回值问题的疑惑的更多相关文章
- Swift2.0语言教程之函数的返回值与函数类型
Swift2.0语言教程之函数的返回值与函数类型 Swift2.0中函数的返回值 根据是否具有返回值,函数可以分为无返回值函数和有返回值函数.以下将会对这两种函数类型进行讲解. Swift2.0中具有 ...
- C语言函数调用及栈帧结构
source:http://blog.csdn.net/qq_29403077/article/details/53205010 一.地址空间与物理内存 (1)地址空间与物理内存是两个完全不同的概念, ...
- C语言 realloc为什么要有返回值,realloc返回值具体解释/(解决随意长度字符串输入问题)。
在C语言操作中会用到大量的内存操作,当中非经常常使用的一个是realloc(). 由字面意思能够知道,该函数的作用是用于又一次分配内存. 使用方式例如以下: NewPtr=(数据类型*)realloc ...
- 013_go语言中的函数多返回值
代码演示 package main import "fmt" func vals() (int, int) { return 3, 7 } func main() { a, b : ...
- C语言中赋值表达式的返回值是什么?
我们或多或少都有过,或者见过将赋值表达式参与运算的情况.这通常会伴随着一些意想不到的问题.今天我就见到了一段奇怪的代码: #include<stdio.h> int main() { ; ...
- r语言 function 指定多个返回值
# Goals: To write functions # To write functions that send back multiple objects. # FIRST LEARN ABOU ...
- 汇编中call printf参数压栈时错误理解
EAX, ECX,EDX,EBX均可以32bit,16bit,8bit访问,如下所示: <-------------------EAX------------------------>|& ...
- C++获取Lua全局变量和执行Lua多参数多返回值函数
C++代码: // LuaAndC.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> #i ...
- [转]WinExec、ShellExecute和CreateProcess及返回值判断方式
[转]WinExec.ShellExecute和CreateProcess及返回值判断方式 http://www.cnblogs.com/ziwuge/archive/2012/03/12/23924 ...
随机推荐
- Android 对程序异常崩溃的捕捉
转载博客:http://blog.csdn.net/i_lovefish/article/details/17719081 以下为异常捕捉处理代码: import java.io.BufferedRe ...
- 取消vs2013在solution中单击打开文件的功能
2013用了一段时间,不错,就是单击会打开文件,有点恼人(因人而异吧).解决方案: 取消红色框框里面的那个checkbox就ok了. 来自为知笔记(Wiz)
- 关于在BAE上部署ThinkPHP框架的问题
现在有点小兴奋,因为在在BAE上部署ThinkPHP框架的问题快折腾一天了,午觉都没睡,不过没白整总算有点结果.不扯淡了,直入正题吧. 之前熟悉ThinkPHP框架,想在BAE上用ThinkPHP做点 ...
- IO通道
本文原创,转载需标明原处. 通道,主要负责传输数据,相当于流,但流只能是输入或输出类型中的其一,而通道则可以兼并二者. 通道的基类是:Channel boolean isOpen() void clo ...
- Sublime Text3下的markdown插件的安装及配置
Sublime Text3下的markdown插件的安装及配置 安装准备--安装Package Control 安装MarkdownEditing 安装Markdown Preview或OmniMar ...
- 【字符编码】Java编码格式探秘
一.前言 在分析Comparable和Comparator的时候,分析到了String类的compareTo方法,String底层是用char[]数组来存放元素,在比较的时候是比较的两个字符串的字符, ...
- 代码的坏味道(2)——过大的类(Large Class)
坏味道--过大的类(Large Class) 特征 一个类含有过多字段.函数.代码行. 问题原因 类通常一开始很小,但是随着程序的增长而逐渐膨胀. 类似于过长函数,程序员通常觉得在一个现存类中添加新特 ...
- 初识Spring
Spring简介 说到Spring就得提到它的作者"Rod Johnson",2002年他编写了<Expert One-on_One java EE设计与开发>一书.在 ...
- C#播放wav文件
C#使用HWQPlayer类播放wav文件 类的代码: using System.IO; using System.Runtime.InteropServices; namespace HoverTr ...
- [WCF编程]11.错误:错误契约
一.错误传播 服务需要向客户端报告特定错误,当WCF默认的错误屏蔽方法并不包含这一实现.另一个重要的问题与传播到客户端有关,即由于异常是针对特定技术的,因此无法跨越服务边界而被共享.要实现无缝的互操作 ...