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 ...
随机推荐
- WPF之资源专题
1.一般程序的资源可以分为四个等级: 数据库中的数据相当于放在仓库里 资源文件里的数据相当于放在旅行箱里 WPF对象资源里的数据相当于携带在背包里 变量中的数据相当于拿在手里 2.资源的查找顺序是沿着 ...
- FFT 的一些技巧
三次变两次 FFT 我们发现: \[(F(x)+iG(x))^2=F(x)^2-G(x)^2+2iF(x)G(x) \] 也就是说,我们把 \(F(x)\) 作为实部,\(G(x)\) 作为虚部,那么 ...
- 以太坊web3开发初步学习
以太坊web3开发初步学习 此文是对https://learnblockchain.cn/2018/04/15/web3-html/的学习再理解. 以太坊智能合约通过使用web3.js前端和智能合约交 ...
- 时间轮机制在Redisson分布式锁中的实际应用以及时间轮源码分析
本篇文章主要基于Redisson中实现的分布式锁机制继续进行展开,分析Redisson中的时间轮机制. 在前面分析的Redisson的分布式锁实现中,有一个Watch Dog机制来对锁键进行续约,代码 ...
- 想要彻底搞懂大厂是如何实现Redis高可用的?看这篇文章就够了!(1.2W字,建议收藏)
高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间. 假设系统一直能够提供服务,我们说系统的可用性是100%.如果 ...
- Wireshark 过滤器的使用
符号 例子 = = tcp.port = = 80 过滤出来TCP包含80端口的数据包 != ip.src != 127.0.0.1 ip的原地址不是127.0.0.1过滤出来 > lp.len ...
- pycharm 服务器连接及一些问题解决
主要介绍一下如何使用pycharm连接服务器并在服务器上炼丹,并对遇到的一个小问题进行说明. 目录 1,SSH连接 2,linux常用命令 3,配置anaconda 4,运行代码 5,一个常见错误 1 ...
- 技术博客——微信小程序的架构与原理
技术博客--微信小程序的架构与原理 在两个月的微信小程序开发过程中,我曾走了不少弯路,也曾被很多现在看来十分可笑的问题所困扰.这些弯路与困扰,基本上都是由于当时对小程序的架构理解不够充分,对小程序的原 ...
- BUAA2020软工作业(四)——结对项目
项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 结对项目作业 我在这个课程的目标是 进一步提高自己的编码能力,工程能力,团队协作能力 这个作业在哪 ...
- cf14D Two Paths(树的直径)
题意: N个点构成一棵树.树枝的长度都是1. 在当中找两条不相交[没有公共点]的路,使得二者长度之积最大. (2 ≤ n ≤ 200) 思路: 一开始思路好麻烦,好麻烦,好麻烦,,,,,,,而且WA, ...