结构体型的参数和返回值的C函数, GCC如何生成代码?

本文探讨GCC在编译C语言中包含结构体型参数和返回值的函数时,如何生成代码,包括参数传递、结构体返回值处理及栈帧结构分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C代码与汇编代码有一点差别很大,即汇编语言程序设计中,各种数据类型之间的差别很小.汇编语言并没有提供诸如C语言结构体的抽象,它只是将C语言的结构体看成一个一个的元素.

 

本文探讨对于函数的参数和返回值有结构体型的函数, GCC是如何生成代码的通过分析代码,一方面探索GCC如何为C语言的结构体生成代码,另一方面分析C语言的函数调用过程和返回过程机制, 深入理解C语言函数调用的过程.

 

 

[待分析的C代码]

/* pass-ret-strt.c */
typedef struct {
	int *p;
	int v;
} str1;

typedef struct {
	int prod;
	int sum;
} str2;

str2 word_sum(str1 s1)
{
	str2 result;
	result.prod = *s1.p * s1.v;
	result.sum  = *s1.p + s1.v;
	return result;
}

int diff(int x, int y)
{
	str1 s1;
	str2 s2;
	
	s1.p = &x;
	s1.v = y;
	s2 = word_sum(s1);  /* 含有返回值 */
	
	return s2.prod - s2.sum;

}

[GCC生成的代码]

使用gcc -O1 -S pass-ret-strt.c的代码(使用gcc1级优化)如下.

.file        "pass-ret-strt.c"
.text
.globlword_sum
.type        word_sum,@function
word_sum:
pushl        %ebp
movl        %esp,%ebp
pushl        %ebx
movl        8(%ebp),%eax
movl        12(%ebp),%edx
movl        16(%ebp),%ecx
movl        %ecx,%ebx
movl        (%edx),%edx
leal        (%edx,%ecx),%ecx
movl        %ecx,4(%eax)
imull        %ebx,%edx
movl        %edx,(%eax)
popl        %ebx
popl        %ebp
ret        $4
.size        word_sum,.-word_sum
.globl diff
.type        diff,@function
diff:
pushl        %ebp
movl        %esp,%ebp
subl        $20,%esp
leal        8(%ebp),%eax
movl        12(%ebp),%edx
leal        -8(%ebp),%ecx
movl        %eax,4(%esp)
movl        %edx,8(%esp)
movl        %ecx,(%esp)
call        word_sum
subl        $4,%esp
movl        -8(%ebp),%eax
movl        -4(%ebp),%edx
subl        %edx,%eax
leave
ret
.size        diff,.-diff
.ident        "GCC:(GNU) 4.1.2 20080704 (Red Hat 4.1.2-52)"
.section        .note.GNU-stack,"",@progbits 

 


分析

————

先看栈帧结构,再分析GCC是如何编译传递和返回结构体的?

 

1.栈帧结构



2.分析结论

(1).  diff函数的局部变量的分配

局部变量的存储,编译器有两种方案:一种是将局部变量直接存储在寄存器中,一种是存储在栈中.但是对于数组/结构体型的局部变量,通常是放在内存中.

diff函数有两个局部变量s1, s2.局部变量s1的唯一作用是初始化word_sum的参数. gcc出于优化的考虑,不会在栈中先分配局部变量s1,再根据s1的值分配实参,而是直接将在栈中分配实参.

观察diff的栈帧,可以看到, s2是直接被当成局部变量存储在栈中,s1直接被当成实参放在实参的位置.

 

(2).参数传递

观察diff栈帧中%esp+4~%esp+8可以看出就是diff函数为word_sum安排的参数.

 

(3).返回结构体

通常, gcc将函数的返回值存储在%eax,但是对于结构体,放在%eax,显然是不可能的.

gcc的处理方法是将返回的结构体的指针作为函数的第一个参数. 这样:

str2 word_sum(str1 s1) ---> void word_sum(str2* ps2, str1 s1).

这样,ps2 = &s2就可以啦. %esp~%esp+8就是word_sum的两个参数.其中%esp存放着返回值s2的地址.

特别注意 word_sum的汇编代码的末尾有"ret $4", 意思是将栈中的ps2弹出.

 

3.小记

(1). C语言的局部变量并不总在栈中.确切地说,为了性能(速度)的考虑,通常要充分利用寄存器但是如果要访问一个局部变量的指针,那就没辙啦,只能将该局部变量放在栈中.

(2). C语言的函数的参数传递并不总是发生比如该例子中的word_sum(s1)并没有发生参数传递.

(3). 可以看出 str2 word_sum(str1 s1) 和void word_sum(str2* ps2, str1s1). 这两种方式实际上是等价的所以前者比后者在性能上并没有降低多少.可以安然接受这种写法了.

 


reference:

1. 深入理解计算机系统(原书第2版)

 

 

(版权所有,转载时请注明作者和出处-dennis_fan-http://blog.youkuaiyun.com/dennis_fan )


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值