Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5760072
  • 博文数量: 675
  • 博客积分: 20301
  • 博客等级: 上将
  • 技术积分: 7671
  • 用 户 组: 普通用户
  • 注册时间: 2005-12-31 16:15
文章分类

全部博文(675)

文章存档

2012年(1)

2011年(20)

2010年(14)

2009年(63)

2008年(118)

2007年(141)

2006年(318)

分类: C/C++

2009-08-25 21:03:11

上个月买了一本《程序员的自我修养---链接、装载与库》,前两天随手翻了几章看了一下,发现一个有意思的问题,就是关于函数传递大尺寸返回值的实现。

一般的函数返回值通过eax寄存器,但是只是针对小尺寸的返回值,现在C89中已经可以允许返回结构体了,因此对于大尺寸的返回值是如何实现的,应该深究一下。

===============================================================================
在《程序员的自我修养---链接、装载与库》中,提到:
毋庸置疑,如果返回值类型的尺寸太大,C语言在函数返回时,会使用一个临时的栈上内存区域作为中转,结果返回值对象被拷贝了两次。

思路理清:
1. 首先main函数在栈上额外开辟一片空间,并将这块空间的一部分作为传递返回值的临时对象,这里称为temp。
2. 将temp对象的地址作为隐藏参数传递给return_test函数
3. return_test函数将数据拷贝到temp对象,并将temp对象的地址用eax传出
4. return_test返回之后,main函数将eax指向的temp对象的内容拷贝给n

用伪代码表示如下:
void return_test(void *temp)
{
  big_thing b;
  b.buf[0] = 0;
  memcpy(temp, &b, sizeof(big_thing));
  eax = temp;
}
int main()
{
  big_thing temp;
  big_thing n;
  return_test(&temp);
  memcpy(&n, eax, sizeof(big_thing));
}

注意:实际上红色部分的那次拷贝是可以优化掉的,去掉临时结构temp,而直接将最终的结构n的地址传递给return_test函数。main代码可以如下进行编写:
int main()
{
  big_thing n;
  return_test(&n);
}
实际上gcc也是这么优化的,起码在我测试的下面3个版本中都是优化的,不存在作者在书中所说的两次拷贝情况。

===============================================================================
但是,我很怀疑上面这句话,gcc不可能对这样的性能缺陷置之不理,就做了下实验。
代码也是采用书上的例子:
typedef struct big_thing
{
    char buf[128];
} big_thing;

big_thing return_test ()
{
    big_thing b;
    b.buf[0] = 0;

    return b;
}

int main()
{
    big_thing n = return_test();

    return 0;
}

我在我的机器上分别使用gcc-3.4、gcc-4.1和gcc-4.3做了一下测试,都没有发现书中所说的拷贝两次的问题。

gcc-3.4.6的结果:
    .file    "t_ret_struct.c"
    .text
.globl return_test
    .type    return_test, @function
return_test:
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %ebx
    subl    $148, %esp
    movl    8(%ebp), %ebx
    movb    $0, -136(%ebp)
    movl    %ebx, %ecx
    leal    -136(%ebp), %edx
    movl    $128, %eax
    movl    %eax, 8(%esp)
    movl    %edx, 4(%esp)
    movl    %ecx, (%esp)
    call    memcpy
    movl    %ebx, %eax
    addl    $148, %esp
    popl    %ebx
    popl    %ebp
    ret    $4
    .size    return_test, .-return_test
.globl main
    .type    main, @function
main:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $152, %esp
    andl    $-16, %esp
    movl    $0, %eax
    addl    $15, %eax
    addl    $15, %eax
    shrl    $4, %eax
    sall    $4, %eax
    subl    %eax, %esp
    leal    -136(%ebp), %eax
    movl    %eax, (%esp)
    call    return_test
    subl    $4, %esp
    movl    $0, %eax
    leave
    ret
    .size    main, .-main
    .section    .note.GNU-stack,"",@progbits
    .ident    "GCC: (GNU) 3.4.6 (Debian 3.4.6-10)"

gcc-4.1下:
    .file    "t_ret_struct.c"
    .text
.globl return_test
    .type    return_test, @function
return_test:
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %ebx
    subl    $148, %esp #temp struct
    movl    8(%ebp), %ebx #dest
    movb    $0, -132(%ebp) #
    movl    %ebx, %ecx
    leal    -132(%ebp), %edx #dest
    movl    $128, %eax
    movl    %eax, 8(%esp)
    movl    %edx, 4(%esp)
    movl    %ecx, (%esp)
    call    memcpy
    movl    %ebx, %eax
    addl    $148, %esp
    popl    %ebx
    popl    %ebp
    ret    $4
    .size    return_test, .-return_test
.globl main
    .type    main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl    -4(%ecx)
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %ecx
    subl    $132, %esp
    leal    -132(%ebp), %eax
    movl    %eax, (%esp)
    call    return_test
    subl    $4, %esp
    movl    $0, %eax
    movl    -4(%ebp), %ecx
    leave
    leal    -4(%ecx), %esp
    ret
    .size    main, .-main
    .ident    "GCC: (GNU) 4.1.3 20080704 (prerelease) (Debian 4.1.2-26)"
    .section    .note.GNU-stack,"",@progbits

gcc-4.3下:
    .file    "t_ret_struct.c"
    .text
.globl return_test
    .type    return_test, @function
return_test:
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %edi
    pushl    %esi
    subl    $140, %esp
    movl    8(%ebp), %eax
    movb    $0, -136(%ebp)
    movl    %eax, -140(%ebp)
    leal    -136(%ebp), %edx
    movl    %edx, -144(%ebp)
    movl    $32, -148(%ebp)
    movl    -140(%ebp), %edi
    movl    -144(%ebp), %esi
    movl    -148(%ebp), %ecx
    rep movsl
    addl    $140, %esp
    popl    %esi
    popl    %edi
    popl    %ebp
    ret    $4
    .size    return_test, .-return_test
.globl main
    .type    main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl    -4(%ecx)
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %ecx
    subl    $132, %esp
    leal    -132(%ebp), %eax
    movl    %eax, (%esp)
    call    return_test
    subl    $4, %esp
    movl    $0, %eax
    movl    -4(%ebp), %ecx
    leave
    leal    -4(%ecx), %esp
    ret
    .size    main, .-main
    .ident    "GCC: (Debian 4.3.4-1) 4.3.4"
    .section    .note.GNU-stack,"",@progbits

-------------------------------------------------------------------------------
实际上,gcc的优化在于因为变量为main中的临时变量,肯定是需要在栈中分配的,如果直接把这个最终的地址传递给return_test的话,实际上就不用再将return_test返回的临时空间的值,再拷贝到最终的地址了。
当然,如果big_thing n是一个全局变量,那么结果就是另外一个样子了,需要真的拷贝两次了。

typedef struct big_thing
{
    char buf[128];
} big_thing;

big_thing return_test ()
{
    big_thing b;
    b.buf[0] = 0;

    return b;
}
big_thing n;
int main()
{
     n = return_test();

    return 0;
}

gcc-4.3下:
    .file    "t.c"
    .text
.globl return_test
    .type    return_test, @function
return_test:
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %edi
    pushl    %esi
    subl    $140, %esp
    movl    8(%ebp), %eax
    movb    $0, -136(%ebp)
    movl    %eax, -140(%ebp)
    leal    -136(%ebp), %edx
    movl    %edx, -144(%ebp)
    movl    $32, -148(%ebp)
    movl    -140(%ebp), %edi
    movl    -144(%ebp), %esi
    movl    -148(%ebp), %ecx
    rep movsl
    addl    $140, %esp
    popl    %esi
    popl    %edi
    popl    %ebp
    ret    $4
    .size    return_test, .-return_test
.globl main
    .type    main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl    -4(%ecx)
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %edi
    pushl    %esi
    pushl    %ecx
    subl    $160, %esp
    leal    -152(%ebp), %eax
    movl    %eax, (%esp)
    call    return_test
    subl    $4, %esp
    movl    $n, -156(%ebp)
    leal    -152(%ebp), %eax
    movl    %eax, -160(%ebp)
    movl    $32, -164(%ebp)
    movl    -156(%ebp), %edi
    movl    -160(%ebp), %esi
    movl    -164(%ebp), %ecx
    rep movsl
    movl    $0, %eax
    leal    -12(%ebp), %esp
    popl    %ecx
    popl    %esi
    popl    %edi
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size    main, .-main
    .comm    n,128,32
    .ident    "GCC: (Debian 4.3.4-1) 4.3.4"
    .section    .note.GNU-stack,"",@progbits

参考:
《程序员的自我修养---链接、加载与库》 10.2.3节 函数返回值传递

阅读(1901) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~