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 ...
随机推荐
- 洛谷4475 巧克力王国(KD-Tree + 维护子树和)
(嘤嘤嘤 又是一个自闭了一晚上的题) qwq果然不是平面上的点的问题,也可以直接用KDTree打暴力 我们对于巧克力直接建kdtree 维护一个\(mx[i],mn[i]\) 但是有一个非常不友好的事 ...
- 【Java虚拟机9】类加载器之命名空间详解
前言 前面介绍类加载器的时候,介绍了一下命名空间这个概念.今天就通过一个例子,来详细了解一下[类加载器的命名空间].然后通过这个例子,我们可以总结一下双亲委托模型的好处与优点. 例1(不删除class ...
- PyCharm永久破解方法,2021最新版本!!!
1,下载破解补丁(已更新到2021.1版本): 关注微信公众号<程序员的时光>,回复破解补丁即可: 下载补丁文件 jetbrains-agent.jar 和importat.txt文件并将 ...
- Spring Security Jwt Token 自动刷新
token的自动刷新 一.功能需求 二.功能分析 1.token 的生成 2.token 的自动延长 3.系统资源的保护 4.用户如何传递 token 三.实现思路 1.生成 token 和 refr ...
- java中延时队列的使用
最近遇到这么一个需求,程序中有一个功能需要发送短信,当满足某些条件后,如果上一步的短信还没有发送出去,那么应该取消这个短信的发送.在翻阅java的api后,发现java中有一个延时队列可以解决这个问题 ...
- 震惊,本Orzer下阶段直接怒送四个笑脸
众所周知,在hzoi帝国中,Wzx是最菜的.那么究竟有多菜呢?下面就和小编一起来看看吧. 近日,hzoi最菜的wzx在第四阶段竟然怒送4个笑脸,同机房神犇直呼wzx太菜了! 以上就是wzx第四阶段怒送 ...
- 必备的60个常用的Linux命令
Linux必学的60个命令Linux提供了大量的命令,利用它可以有效地完成大量的工 作,如磁盘操作.文件存取.目录操作.进程管理.文件权限设定等.所以,在Linux系统上工作离不开使用系统提供的命令. ...
- 网络原理数据链路层之差错控制(检错编码和纠错编码)->(奇偶校验码、CRC循环冗余码、海明码)
文章转自:https://blog.csdn.net/weixin_43914604/article/details/104864783 学习课程:<2019王道考研计算机网络> 学习目的 ...
- best-time-to-buy-and-sell-stock-iii leetcode C++
Say you have an array for which the i th element is the price of a given stock on day i. Design an a ...
- x64 InlineHook 黑魔法
目录 x64 InlineHook 黑魔法 为什么不能用X86 的HOOK方式? 原理:jmp + rip 进行寻址6字节方式跳转 手动InlineHook 临时地址x(找一块空内存) 计算偏移 源地 ...