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的代码(使用gcc的1级优化)如下.
.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:
(版权所有,转载时请注明作者和出处-dennis_fan-http://blog.youkuaiyun.com/dennis_fan )