c语言是如何处理函数调用的?
1. 要编译的测试代码:
int plus(int x, int y) {
return x + y;
} int main(void) {
return plus(3, 4);
}
2. main中return语句语法分析
if (equal(tok, "return")) {
Node *node = new_node(ND_RETURN, tok);
if (consume(rest, tok->next, ";"))
return node; Node *exp = expr(&tok, tok->next);
*rest = skip(tok, ";"); node->lhs = exp;
return node;
}
2.1 当前token为return,则创建类型为ND_RETURN的node。
2.2 由于return后面跟着plus(3, 4),所以调用expr函数解析这个表达式。
2.3 跳过";"符号。
2.4 将类型为ND_RETURN的node的左表达式设置为类型为ND_CAST的node。
3. expr的处理
3.1 解析"plus"
if (tok->kind == TK_IDENT) {
VarScope *sc = find_var(tok); return new_var_node(sc->var, tok);
}
static Node *new_var_node(Obj *var, Token *tok) {
Node *node = new_node(ND_VAR, tok);
node->var = var;
return node;
}
当token类型为TK_IDENT时,从符号表中找到代表plus函数的node,这个node
是在解析plus函数时创建的,就不详细分析了。
创建新的类型为ND_VAR的node,这个node的var域为代表plus函数的node。
if (equal(tok, "(")) {
node = funcall(&tok, tok->next, node);
}
if (ty->kind != TY_STRUCT && ty->kind != TY_UNION)
exp = new_cast(exp, current_fn->ty->return_ty); node->lhs = exp;
return node;
解析完函数名plus后,继续解析函数参数调用,如果plus后面跟着是"(",则判断为函数调用,
于是调用funcall函数,这个函数的参数node即为上面创建的类型为ND_VAR的node。
创建完类型为ND_FUNCALL的node,又调用new_cast创建类型为ND_CAST的节点,这个节点
的左表达式为类型为ND_FUNCALL的node。return的node为类型为ND_RETURN,它的左表达式为
ND_CAST的node。
3.2 funcall函数
Node head = {};
Node *cur = &head; while (!equal(tok, ")")) {
if (cur != &head)
tok = skip(tok, ","); Node *arg = assign(&tok, tok);if (param_ty) {
if (param_ty->kind != TY_STRUCT && param_ty->kind != TY_UNION)
arg = new_cast(arg, param_ty);
param_ty = param_ty->next;
} else if (arg->ty->kind == TY_FLOAT) {
// If parameter type is omitted (e.g. in "..."), float
// arguments are promoted to double.
arg = new_cast(arg, ty_double);
} cur = cur->next = arg;
} *rest = skip(tok, ")"); Node *node = new_unary(ND_FUNCALL, fn, tok);
node->func_ty = ty;
node->ty = ty->return_ty;
node->args = head.next; return node;
在funcall函数中调用assign函数解析"(3,4)",3被解析为类型为ND_NUM的node,
Node *new_cast(Node *expr, Type *ty) {
add_type(expr); Node *node = calloc(1, sizeof(Node));
node->kind = ND_CAST;
node->tok = expr->tok;
node->lhs = expr;
node->ty = copy_type(ty);
return node;
}
调用new_cast函数,创建类型为ND_CAST的node节点,这个节点的左表达式为代表3的node。
跳过",",继续调用assign解析4,4也被解析为ND_NUM的node,继续调用new_cast,创建类型为
ND_CAST的node节点,这个节点的左表达式为代表4的node。
跳出循环,跳过")"。
new_unary函数中创建类型为ND_FUNCALL的node,这个node的左表达式为plus函数的node,
args参数为解析"(3,4)"生成的两个类型为ND_CAST的node。
4. 生成汇编语言
static void gen_stmt(Node *node) {
switch (node->kind) {
case ND_RETURN:
if (node->lhs) {
gen_expr(node->lhs);
}
println(" jmp .L.return.%s", current_fn->name);
...
}
判断node节点为ND_RETURN,则调用gen_expr处理类型为ND_CAST的节点。
static void gen_expr(Node *node) {
switch (node->kind) {
case ND_FUNCALL: {
int stack_args = push_args(node);
gen_expr(node->lhs);
for (Node *arg = node->args; arg; arg = arg->next) {
pop(argreg64[gp++]);
println(" mov %%rax, %%r10");
println(" call *%%r10");
println(" add $%d, %%rsp", stack_args * 8);
}
case ND_VAR:
gen_addr(node);return; case ND_CAST:
gen_expr(node->lhs);
cast(node->lhs->ty, node->ty);
return;
...
}
...
4.1 判断为ND_CAST类型的node,则调用gen_expr,参数为类型为ND_FUNCALL的node。
4.2 调用push_args函数依次生成汇编语句
"mov rax, 4"
"push rax"
"mov rax, 3"
"push rax"
将4和3压入栈。
4.3 递归调用gen_expr,参数为类型为ND_VAR的node。
4.4 调用gen_addr函数生成汇编代码"lea rax, plus",将plus函数地址载入rax寄存器。
4.5 pop语句生成汇编代码"pop rdi","pop rsi",将3弹入rdi寄存器,将4弹入rsi寄存器,
plus函数中从这两个寄存器中读取参数。
4.6 生成汇编代码
"mov r10, rax"
"call r10"
"add rsp, 0"
将plus函数地址从rax载入r10寄存器,call语句完成调用plus函数,由于没有分配栈空间传递参数
,所以这里不需要修改rsp寄存器的值。
4.7 "jmp .L.return.main",跳转到main函数的结尾处,实现return功能。
c语言是如何处理函数调用的?的更多相关文章
- 为何C语言(的函数调用)需要堆栈,而汇编语言不需要
转自:Uboot中start.S源码中指令级的详尽解析 green-waste为何 C 语言(的函数调用)需要堆栈,而汇编语言却需要堆栈之前看了很多关亍uboot的分析,其中就有说要为C语言的运行,准 ...
- C语言常见的函数调用
C语言常见的函数调用 isatty,函数名,主要功能是检查设备类型,判断文件描述词是否为终端机. 函数名: isatty 用 法: int isatty(int desc); 返回值:如果参数desc ...
- 为何C语言(的函数调用)需要堆栈,而汇编语言却不需要堆栈
http://www.cnblogs.com/myblesh/archive/2012/04/07/2435737.html 之前看了很多关于uboot的分析,其中就有说要为C语言的运行,准备好堆栈. ...
- [汇编与C语言关系]1.函数调用
对于以下程序: int bar(int c, int d) { int e = c + d; return e; } int foo(int a, int b) { return bar(a, b); ...
- Go语言是如何处理栈的
转自:http://tonybai.com/2014/11/05/how-stacks-are-handled-in-go/ Go 1.4Beta1刚刚发布,在Go 1.4Beta1中,Go语言的st ...
- 在C语言中破坏函数调用堆栈
// 这段代码显示,在C语言修改函数的返回地址 int test1() { ; } int test2(int a) { *(&a-) = (int)test1; // 将返回地址修改为tes ...
- 递归转手工栈处理的一般式[C语言]
是任意形式的递归,是化解的一般式. 主题所谓的“递归调用化解为栈处理”,意思是,将递归函数调用化解为“一个由stack_push stack_pop stack_top等函数调用组成的循环式子”.这里 ...
- C语言经典面试题 与 C语言面试宝典
1 预处理 问题1:什么是预编译?何时需要预编译? 答: 预编译又称预处理,是整个编译过程最先做的工作,即程序执行前的一些预处理工作.主要处理#开头的指令.如拷贝#include包含的文件代码.替换# ...
- [汇编与C语言关系]2. main函数与启动例程
为什么汇编程序的入口是_start,而C程序的入口是main函数呢?以下就来解释这个问题 在<x86汇编程序基础(AT&T语法)>一文中我们汇编和链接的步骤是: $ as hell ...
随机推荐
- 山顶点提取(ArcPy实现)
一.背景 山顶点指哪些在特定邻域分析范围内,该点都比周围点高的区域.山顶点是地形的重要特征点,它的分布与密度反映了地貌的发育特征,同时也制约着地貌发育.因此,如何基于DEM数据正确有效的提取山顶点,在 ...
- Java(46)类加载器
作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201673.html 博客主页:https://www.cnblogs.com/testero ...
- Get Mingw-w64 via MSYS2
Get Mingw-w64 via MSYS2 Get the latest version of Mingw-w64 via MSYS2, which provides up-to-date nat ...
- 【Java虚拟机6】Java内存模型(Java篇)
什么是Java内存模型 <Java虚拟机规范>中曾试图定义一种"Java内存模型"(Java Memory Model,JMM)来屏蔽各种硬件和操作系统的内存访问差异, ...
- 【c++ Prime 学习笔记】第4章 表达式
表达式由一个或多个运算对象组成,对表达式求值返回结果. 字面值和变量是最简单的表达式 把运算符和运算对象组合可得到复杂表达式. 4.1 基础 4.1.1 基本概念 一元运算符作用于一个对象,如取地址符 ...
- 洛谷 P6075 [JSOI2015]子集选取
链接:P6075 前言: 虽然其他大佬们的走分界线的方法比我巧妙多了,但还是提供一种思路. 题意: %&¥--@#直接看题面理解罢. 分析过程: 看到这样的题面我脑里第一反应就是DP,但是看到 ...
- A*,IDA*—高档次的暴搜
A*通过评价函数来判断当前状态是否可以到达最终状态(即可行性剪枝),来减少不必要的搜索. 例题--P2324 [SCOI2005]骑士精神 我们通过当前不在指定位置上的棋子个数为评价函数,\(used ...
- MyBatis源码分析(五):MyBatis Cache分析
一.Mybatis缓存介绍 在Mybatis中,它提供了一级缓存和二级缓存,默认的情况下只开启一级缓存,所以默认情况下是开启了缓存的,除非明确指定不开缓存功能.使用缓存的目的就是把数据保存在内存中,是 ...
- 集合栈 牛客网 程序员面试金典 C++ Python
集合栈 牛客网 程序员面试金典 C++ Python 题目描述 请实现一种数据结构SetOfStacks,由多个栈组成,其中每个栈的大小为size,当前一个栈填满时,新建一个栈.该数据结构应支持与普通 ...
- hdu 2058 The sum problem(简单因式分解,,)
Problem Description Given a sequence 1,2,3,......N, your job is to calculate all the possible sub-se ...