Chinaunix

标题: 请教:C语言函数的返回值类型能否为结构体? [打印本页]

作者: yazi0127    时间: 2010-01-26 15:27
标题: 请教:C语言函数的返回值类型能否为结构体?
如题。如果可以的话,正确的使用方法应该怎么做?能否举个例子?
作者: 松饼熊    时间: 2010-01-26 16:48
随手写的一个例子,有些地方很不严谨,高手请蛋定。

编译运行环境:Dev C++

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int    m_Member1;
    int    m_Member2;
    char m_String[20];
}FUNCTION_STRUCT;

FUNCTION_STRUCT ReturnStruct(void)
{
    FUNCTION_STRUCT internalData;
    internalData.m_Member1 = 1;
    internalData.m_Member2 = 2;
    strcpy(&(internalData.m_String[0]), "Hello World!\n");

    return internalData;
}

int main(int argc, char *argv[])
{
    FUNCTION_STRUCT externalData;
    externalData = ReturnStruct();
    
    printf("%d, %d, %s",
            externalData.m_Member1,
            externalData.m_Member2,
            &externalData.m_String[0]);
    
    system("AUSE");    
    return 0;
}


[ 本帖最后由 松饼熊 于 2010-1-26 16:49 编辑 ]
作者: daybreakcx    时间: 2010-01-26 16:59
可以的,但是我认为但是如果结构体比较大的话,直接返回结构体不太好,因为如果外部用一个变量接着就还需要一次拷贝,可以用指针传
作者: 老手    时间: 2010-01-26 17:02
原帖由 daybreakcx 于 2010-1-26 16:59 发表
可以的,但是我认为但是如果结构体比较大的话,直接返回结构体不太好,因为如果外部用一个变量接着就还需要一次拷贝,可以用指针传


似懂非懂 , 误导人了!
作者: daybreakcx    时间: 2010-01-26 17:05
原帖由 老手 于 2010-1-26 17:02 发表


似懂非懂 , 误导人了!

还请赐教
作者: c/unix    时间: 2010-01-26 17:12
提示: 作者被禁止或删除 内容自动屏蔽
作者: 老手    时间: 2010-01-26 17:15
原帖由 c/unix 于 2010-1-26 17:12 发表


意思是你传的时候可以用指针传,返回就不要用指针了,如果是栈上的怎么办。


不是这个意思. 再大的结构都可以返回 , 没有所谓拷贝的过程.

你们可以想想 , 如果要拷贝 , 具体怎么拷贝 ?!
作者: daybreakcx    时间: 2010-01-26 17:15
传入时候是指针传入被操作结构体地址,直接原地操作,我的意思是这样
作者: 老手    时间: 2010-01-26 17:23
原帖由 daybreakcx 于 2010-1-26 17:15 发表
传入时候是指针传入被操作结构体地址,直接原地操作,我的意思是这样


这就说明你还没太懂.
作者: daybreakcx    时间: 2010-01-26 17:23
原帖由 老手 于 2010-1-26 17:15 发表


不是这个意思. 再大的结构都可以返回 , 没有所谓拷贝的过程.

你们可以想想 , 如果要拷贝 , 具体怎么拷贝 ?!

结构体返回的话一般来说都不会只有一个成员变量,所以一般返回后需要一个结构体变量接着它的返回值,就像2楼的程序一样,平时我一般都是这么干的,我认为这里就有一次整个结构体的拷贝,不知道你所说的是什么方法,愿闻其详
作者: 老手    时间: 2010-01-26 17:25
原帖由 daybreakcx 于 2010-1-26 17:23 发表

结构体返回的话一般来说都不会只有一个成员变量,所以一般返回后需要一个结构体变量接着它的返回值,就像2楼的程序一样,平时我一般都是这么干的,我认为这里就有一次整个结构体的拷贝,不知道你所说的是什么 ...


你给我解释拷贝过程 , 你将漏洞百出 , 无法自圆其说.
作者: wsw1wsw2    时间: 2010-01-26 17:40
原帖由 老手 于 2010-1-26 17:25 发表


你给我解释拷贝过程 , 你将漏洞百出 , 无法自圆其说.


返回的结构体就是根据该结构的大小,有几个字节拷贝出几个字节。
有怀疑可以看看编译出来的汇编。

C语言中一般不怎么玩,一般是传给函数一个结构体指针,由该函数修改这个结构。如果非要函数返回结构实体,也没问题。就是老一辈的程序员不太这么干而已。
作者: 老手    时间: 2010-01-26 17:42
原帖由 wsw1wsw2 于 2010-1-26 17:40 发表


返回的结构体就是根据该结构的大小,有几个字节拷贝出几个字节。
有怀疑可以看看编译出来的汇编。

C语言中一般不怎么玩,一般是传给函数一个结构体指针,由该函数修改这个结构。如果非要函数返回结构实 ...


拷贝? 在返回之前还是之后 ?
作者: 老手    时间: 2010-01-26 17:45
在之前, 拷贝的目的地在哪里 ?
之后 , 拷贝的源又在哪里?
作者: OwnWaterloo    时间: 2010-01-26 18:00
对"大"结构体 S :

1. 一个返回S的函数f:

S f(parameters) {
    S result;
    operation_on(&result, parameters);
    return result;
}

2. 一个f的调用点:
S s;
s = f(arguments);


-------- -------- -------- --------
有可能编译器实际上是这么实现的:

f的定义:
void f(S* presult, parameters) {
      operation_on(presult, parameters);
}

f的调用点:
S s;
f(&s, arguments);


标准好像没有要求这么实现, 但msvc和gcc都是这样干的, 当结构体足够"大"的时候。

所以, "因为返回结构体效率低, 应该添加一个结构体指针作为返回参数" 不一定是必要的。
有可能编译器会帮你做这个事情 —— 而且是高效的做。
编辑器才知道"大"的阈值, 什么时候应该进行这样的转换。
作者: daybreakcx    时间: 2010-01-26 18:06
刚才去生成了一个汇编看了一下,好像是调用者先在堆栈里头开结构体大小的空间,然后将地址送eax,后传入调用
作者: daybreakcx    时间: 2010-01-26 18:11
可是我还是认为经由函数操作后,调用者利用这个原先传入的自己堆栈中的地址值如果进行s = f(arguments)这样的操作的时候,需要把指针指向堆栈内的内容进行到s对应内存的拷贝,这里就消耗拷贝时间了
作者: 老手    时间: 2010-01-26 18:11
FUNCTION_STRUCT ReturnStruct(void)
{
    FUNCTION_STRUCT internalData;
    internalData.m_Member1 = 1;
    internalData.m_Member2 = 2;
    strcpy(&(internalData.m_String[0]), "Hello World!\n");

    return internalData;
}


难道有人认为 internalData 就是拷贝之源 ?
那么按照一般的理解internalData可是在堆栈中生成的 , 如果是这样的话 ,函数一返回 , internalData岂不是没了.

"松饼熊"是正确的,他在2楼的程序实际上是这样的:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int    m_Member1;
    int    m_Member2;
    char m_String[20];
}FUNCTION_STRUCT;

void ReturnStruct(FUNCTION_STRUCT *internalData)
{
    internalData->m_Member1 = 1;
    internalData->m_Member2 = 2;
    strcpy(internalData->m_String, "Hello World!\n");   
}

int main(int argc, char *argv[])
{
    FUNCTION_STRUCT externalData;
    ReturnStruct(&externalData);
    
    printf("%d, %d, %s",
            externalData.m_Member1,
            externalData.m_Member2,
            &externalData.m_String[0]);
    
    system("AUSE");   
    return 0;
}



这跟 "OneWaterloo" 说的结构体大小没有关系 , 都是这样的.

[ 本帖最后由 老手 于 2010-1-26 18:14 编辑 ]
作者: ZSMDEV    时间: 2010-01-26 18:22
of course
作者: OwnWaterloo    时间: 2010-01-26 18:40
标题: 回复 #18 老手 的帖子
原帖由 老手 于 2010-1-26 18:11 发表
这跟 "OneWaterloo" 说的结构体大小没有关系 , 都是这样的.


是吗?  你从汇编角度看看:

struct Point2D32S {
      int x;
      int y;
};
是怎么返回的?

struct Point2D16S {
      short x;
      short y;
};

struct atomic_t {
      int val;
};
又是怎么返回的?


然后再说说, 是否和结构体大小有关系。
作者: 老手    时间: 2010-01-26 21:07
下面这个程序没问题吧
#include <stdio.h>

struct atomic_t {
  int val ;
} ;

struct atomic_t GetAtomic()
{
  struct atomic_t a ;
  
  a.val = 0x33 ;

  return a ;
}


int main()
{
  struct atomic_t b ;

  b = GetAtomic() ;

  return b.val>0 ? 1 : 0 ;
}


去掉截屏 , 免得flw老大不高兴.


[ 本帖最后由 老手 于 2010-1-26 22:30 编辑 ]

ret_stru.jpg (291 Bytes, 下载次数: 123)

ret_stru.jpg

作者: 老手    时间: 2010-01-26 21:18
那就从汇编的角度看看吧:
GetAtomic proc near

var_4= dword ptr -4
arg_0= dword ptr  8

push    ebp
mov     ebp, esp
sub     esp, 10h
mov     edx, [ebp+arg_0]
mov     [ebp+var_4], 33h
mov     eax, [ebp+var_4]
mov     [edx], eax
mov     eax, edx
leave
retn    4
GetAtomic endp



public main
main proc near

var_24= dword ptr -24h
arg_0= byte ptr  4

lea     ecx, [esp+arg_0]
and     esp, 0FFFFFFF0h
push    dword ptr [ecx-4]
push    ebp
mov     ebp, esp
push    ecx
sub     esp, 18h
lea     eax, [ebp-18h]
mov     [esp+24h+var_24], eax
call    GetAtomic
sub     esp, 4
mov     eax, [ebp-18h]
mov     [ebp-8], eax
mov     eax, [ebp-8]
test    eax, eax
setnle  al
movzx   eax, al
mov     ecx, [ebp-4]
leave
lea     esp, [ecx-4]
retn
main endp


gcc 4.1.1 编译 O2优化 , 请"OneWaterloo"看看VC++是不是这样的, 我没有VC.
作者: OwnWaterloo    时间: 2010-01-26 22:14
标题: 回复 #22 老手 的帖子
我写毕业论文时, 就仔细研究过返回值类型对thunk技术的影响。
应该不会记错。

既然你这么认真, 那我就再写点玩具代码验证一下:

-------- -------- -------- return_structure.c
typedef struct {
      int x;
      int y;
} Point2D32S;

Point2D32S point2d32s(int x,int y) {
      Point2D32S result = {x,y};
      return result;
}

typedef struct {
      short x;
      short y;
} Point2D16S;

Point2D16S point2d16s(short x,short y) {
      Point2D16S result = {x,y};
      return result;
}

typedef struct {
      int val;
} atomic_t;

atomic_t atomic(int val) {
      atomic_t result = {val};
      return result;
}

-------- -------- -------- msvc6  cl /W3 /O2 /FAs /c return_structure.c /Favc6.asm

PUBLIC        _point2d32s
;        COMDAT _point2d32s
_TEXT        SEGMENT
_x$ = 8
_y$ = 12
_point2d32s PROC NEAR                                        ; COMDAT

; 7    :       Point2D32S result = {x,y};

        mov        eax, DWORD PTR _x$[esp-4]
        mov        edx, DWORD PTR _y$[esp-4]

; 8    :       return result;
; 9    : }

        ret        0
_point2d32s ENDP
_TEXT        ENDS

PUBLIC        _point2d16s
;        COMDAT _point2d16s
_TEXT        SEGMENT
_x$ = 8
_y$ = 12
_result$ = 8
_point2d16s PROC NEAR                                        ; COMDAT

; 17   :       Point2D16S result = {x,y};

        mov        ax, WORD PTR _x$[esp-4]
        mov        cx, WORD PTR _y$[esp-4]
        mov        WORD PTR _result$[esp-4], ax
        mov        WORD PTR _result$[esp-2], cx

; 18   :       return result;

        mov        eax, DWORD PTR _result$[esp-4]

; 19   : }

        ret        0
_point2d16s ENDP
_TEXT        ENDS


PUBLIC        _atomic
;        COMDAT _atomic
_TEXT        SEGMENT
_val$ = 8
_atomic        PROC NEAR                                        ; COMDAT

; 26   :       atomic_t result = {val};
; 27   :       return result;

        mov        eax, DWORD PTR _val$[esp-4]

; 28   : }

        ret        0
_atomic        ENDP
_TEXT        ENDS
END


-------- -------- -------- msvc8  cl /W3 /O2 /FAs /c return_structure.c /Favc8.asm
PUBLIC        _point2d32s
_TEXT        SEGMENT
_x$ = 8                                                        ; size = 4
_y$ = 12                                                ; size = 4
_point2d32s PROC                                        ; COMDAT

; 7    :       Point2D32S result = {x,y};

        mov        eax, DWORD PTR _x$[esp-4]
        mov        edx, DWORD PTR _y$[esp-4]

; 8    :       return result;
; 9    : }

        ret        0
_point2d32s ENDP
_TEXT        ENDS


PUBLIC        _point2d16s
; Function compile flags: /Ogtpy
;        COMDAT _point2d16s
_TEXT        SEGMENT
_result$ = 8                                                ; size = 4
_x$ = 8                                                        ; size = 2
_y$ = 12                                                ; size = 2
_point2d16s PROC                                        ; COMDAT

; 17   :       Point2D16S result = {x,y};

        mov        ax, WORD PTR _x$[esp-4]
        mov        cx, WORD PTR _y$[esp-4]
        mov        WORD PTR _result$[esp-4], ax
        mov        WORD PTR _result$[esp-2], cx

; 18   :       return result;

        mov        eax, DWORD PTR _result$[esp-4]

; 19   : }

        ret        0
_point2d16s ENDP
_TEXT        ENDS


PUBLIC        _atomic
; Function compile flags: /Ogtpy
;        COMDAT _atomic
_TEXT        SEGMENT
_val$ = 8                                                ; size = 4
_atomic        PROC                                                ; COMDAT

; 26   :       atomic_t result = {val};
; 27   :       return result;

        mov        eax, DWORD PTR _val$[esp-4]

; 28   : }

        ret        0
_atomic        ENDP
_TEXT        ENDS
END



-------- -------- -------- msvc9  cl /W3 /O2 /FAs /c return_structure.c /Favc9.asm
PUBLIC        _point2d32s
_TEXT        SEGMENT
_x$ = 8                                                        ; size = 4
_y$ = 12                                                ; size = 4
_point2d32s PROC                                        ; COMDAT

; 7    :       Point2D32S result = {x,y};

        mov        eax, DWORD PTR _x$[esp-4]
        mov        edx, DWORD PTR _y$[esp-4]

; 8    :       return result;
; 9    : }

        ret        0
_point2d32s ENDP
_TEXT        ENDS


PUBLIC        _point2d16s
; Function compile flags: /Ogtpy
;        COMDAT _point2d16s
_TEXT        SEGMENT
_result$ = 8                                                ; size = 4
_x$ = 8                                                        ; size = 2
_y$ = 12                                                ; size = 2
_point2d16s PROC                                        ; COMDAT

; 17   :       Point2D16S result = {x,y};

        mov        ax, WORD PTR _x$[esp-4]
        mov        cx, WORD PTR _y$[esp-4]
        mov        WORD PTR _result$[esp-4], ax
        mov        WORD PTR _result$[esp-2], cx

; 18   :       return result;

        mov        eax, DWORD PTR _result$[esp-4]

; 19   : }

        ret        0
_point2d16s ENDP
_TEXT        ENDS


PUBLIC        _atomic
; Function compile flags: /Ogtpy
;        COMDAT _atomic
_TEXT        SEGMENT
_val$ = 8                                                ; size = 4
_atomic        PROC                                                ; COMDAT

; 26   :       atomic_t result = {val};
; 27   :       return result;

        mov        eax, DWORD PTR _val$[esp-4]

; 28   : }

        ret        0
_atomic        ENDP
_TEXT        ENDS
END



如果你看不懂intel的汇编格式, 这里还有at &t的。

-------- -------- -------- gcc4.4.0  gcc -Wall -O2 -S return_structure.c -o gcc4.4.0.s

        .p2align 2,,3
.globl _point2d32s
        .def        _point2d32s;        .scl        2;        .type        32;        .endef
_point2d32s:
        pushl        %ebp
        movl        %esp, %ebp
        movl        8(%ebp), %eax
        movl        12(%ebp), %edx
        leave
        ret
        .p2align 2,,3
.globl _point2d16s
        .def        _point2d16s;        .scl        2;        .type        32;        .endef
_point2d16s:
        pushl        %ebp
        movl        %esp, %ebp
        movzwl        12(%ebp), %eax
        sall        $16, %eax
        movw        8(%ebp), %ax
        leave
        ret
        .p2align 2,,3
.globl _atomic
        .def        _atomic;        .scl        2;        .type        32;        .endef
_atomic:
        pushl        %ebp
        movl        %esp, %ebp
        movl        8(%ebp), %eax
        leave
        ret


-------- -------- -------- gcc3.4.5  gcc -Wall -O2 -S return_structure.c -o gcc3.4.5.s
        .p2align 4,,15
.globl _point2d32s
        .def        _point2d32s;        .scl        2;        .type        32;        .endef
_point2d32s:
        pushl        %ebp
        movl        %esp, %ebp
        movl        8(%ebp), %eax
        movl        12(%ebp), %edx
        popl        %ebp
        ret
        .p2align 4,,15
.globl _point2d16s
        .def        _point2d16s;        .scl        2;        .type        32;        .endef
_point2d16s:
        pushl        %ebp
        andl        $-65536, %eax
        movl        %esp, %ebp
        movzwl        8(%ebp), %edx
        orl        %edx, %eax
        andl        $65535, %eax
        movzwl        12(%ebp), %edx
        popl        %ebp
        sall        $16, %edx
        orl        %edx, %eax
        ret
        .p2align 4,,15
.globl _atomic
        .def        _atomic;        .scl        2;        .type        32;        .endef
_atomic:
        pushl        %ebp
        movl        %esp, %ebp
        movl        8(%ebp), %eax
        popl        %ebp
        ret



-------- -------- -------- 总结
Point2D32S 返回值在eax, edx中。
Point2D16S 返回值压缩到eax中。
atomic_t      返回值在eax中。

如果gcc加上 -fomit-frame-pointer, 应该会生成完全相同的代码。
作者: flw    时间: 2010-01-26 22:15
最讨厌发截屏的,一看就不专业。
作者: w_anthony    时间: 2010-01-26 23:31
最烦那些一上来就装X的,摆出一付自己高人一等架势的家伙。就算你再牛X,谦虚一点不行么?更何况凡是人,都有出错的可能,给别人点面子,也给自己留条后路。

struct A
{
&nbsp;&nbsp;&nbsp;&nbsp;int i;
};

struct A func()
{
&nbsp;&nbsp;&nbsp;&nbsp;struct A a;
&nbsp;&nbsp;&nbsp;&nbsp;a.i = 0;
&nbsp;&nbsp;&nbsp;&nbsp;return a;
}


编译后基本上相当于(VC编译的)

void func(struct A* p)
{
&nbsp;&nbsp;&nbsp;&nbsp;struct A a;
&nbsp;&nbsp;&nbsp;&nbsp;a.i = 0;
&nbsp;&nbsp;&nbsp;&nbsp;*p = a;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//拷贝发生在这里

}


如果是C++,搞个拷贝构造函数并在其中printf,会发现有输出。

个人认为定义为void func(struct A* p)挺好,至少不用依赖编译器去优化。
作者: 老手    时间: 2010-01-26 23:44
原帖由 w_anthony 于 2010-1-26 23:31 发表
最烦那些一上来就装X的,摆出一付自己高人一等架势的家伙。就算你再牛X,谦虚一点不行么?更何况凡是人,都有出错的可能,给别人点面子,也给自己留条后路。

void func(struct A* p)
{
&nbsp;&nbsp;&nbsp;&nbsp;struct A a;
&nbsp;&nbsp;&nbsp;&nbsp;a.i = 0;
&nbsp;&nbsp;&nbsp;&nbsp;*p = a;        //拷贝发生在这里


}



请搞清楚再说.
作者: pmerofc    时间: 2010-01-26 23:45
提示: 作者被禁止或删除 内容自动屏蔽
作者: 老手    时间: 2010-01-26 23:47
回复 #23 OwnWaterloo 的帖子

以你的结构,结果:

[root@Server01 struct]# cat test2.c
typedef struct {
  int x;
  int y;
} Point2D32S;

Point2D32S point2d32s(int x,int y) {
  Point2D32S result = {x,y};
  return result;
}

typedef struct {
  short x;
  short y;
} Point2D16S;

Point2D16S point2d16s(short x,short y) {
  Point2D16S result = {x,y};
  return result;
}

typedef struct {
  int val;
} atomic_t;

atomic_t atomic(int val) {
  atomic_t result = {val};
  return result;
}

int main()
{
  Point2D32S  a ;

  a = point2d32s(0x55 , 0xAA) ;

  return a.x > a.y ? 1 : 0 ;
  
}
[root@Server01 struct]# gcc -Wall -O2 -fomit-frame-pointer -S test2.c
[root@Server01 struct]#ls *.s
test2.s
[root@Server01 struct]# cat test2.s
        .file        "test2.c"
        .text
        .p2align 4,,15
.globl point2d32s
        .type        point2d32s, @function
point2d32s:
        movl        4(%esp), %eax
        movl        12(%esp), %edx
        movl        %edx, 4(%eax)
        movl        8(%esp), %edx
        movl        %edx, (%eax)
        ret        $4
        .size        point2d32s, .-point2d32s
        .p2align 4,,15
.globl point2d16s
        .type        point2d16s, @function
point2d16s:
        movl        4(%esp), %eax
        movl        12(%esp), %edx
        movw        %dx, 2(%eax)
        movl        8(%esp), %edx
        movw        %dx, (%eax)
        ret        $4
        .size        point2d16s, .-point2d16s
        .p2align 4,,15
.globl atomic
        .type        atomic, @function
atomic:
        movl        4(%esp), %eax
        movl        8(%esp), %edx
        movl        %edx, (%eax)
        ret        $4
        .size        atomic, .-atomic
        .p2align 4,,15
.globl main
        .type        main, @function
main:
        leal        4(%esp), %ecx
        andl        $-16, %esp
        pushl        -4(%ecx)
        pushl        %ecx
        subl        $20, %esp
        leal        12(%esp), %eax
        movl        %eax, (%esp)
        movl        $170, 8(%esp)
        movl        $85, 4(%esp)
        call        point2d32s
        subl        $4, %esp
        movl        12(%esp), %eax
        movl        16(%esp), %edx
        cmpl        %eax, %edx
        setl        %al
        addl        $20, %esp
        popl        %ecx
        movzbl        %al, %eax
        leal        -4(%ecx), %esp
        ret
        .size        main, .-main
        .ident        "GCC: (GNU) 4.1.1 20070105 (Red Hat 4.1.1-52)"
        .section        .note.GNU-stack,"",@progbits
[root@Server01 struct]#
作者: OwnWaterloo    时间: 2010-01-26 23:53
原帖由 w_anthony 于 2010-1-26 23:31 发表
最烦那些一上来就装X的,摆出一付自己高人一等架势的家伙。就算你再牛X,谦虚一点不行么?更何况凡是人,都有出错的可能,给别人点面子,也给自己留条后路。

struct A
{
    int i;
};

struct A func()
{
    struct A a;
    a.i = 0;
    return a;
}

编译后基本上相当于(VC编译的)

void func(struct A* p)
{
    struct A a;
    a.i = 0;
    *p = a;        //拷贝发生在这里

}


你试过没有, 你确定?
23楼的汇编代码你看没有?  哪一个函数会""内存???
上面的有vc6、8、9的汇编输出, 你说的VC又是哪个版本?


再者, 只要有一个反例,就可以证明对结构体的返回并不全是采用隐藏的指针参数
反例我已经举出, 而且是5个。
所以22楼的汇编代码我看不懂(nasm?) 也不需要看懂。


原帖由 w_anthony 于 2010-1-26 23:31 发表
个人认为定义为void func(struct A* p)挺好,至少不用依赖编译器去优化。

只能说明你够牛,  你比编译器还了解如何能够生成高效代码。
比如可以用eax返回的, 非要去写一次内存。  这一定比编译器采取的方式更优化, 不是吗?
作者: OwnWaterloo    时间: 2010-01-26 23:59
标题: 回复 #28 老手 的帖子
"以我的代码"并不能证明什么。

看29楼, 只要能够举出一个没有采用隐式指针参数的反例, 就可以说明这个结论是错误的:

原帖由 老手 于 2010-1-26 18:11 发表
这跟 "OneWaterloo" 说的结构体大小没有关系 , 都是这样的.


结论是 : 并不都是这样。

上面5份代码, 全都是通过寄存器返回的, 没有内存的代码。
作者: w_anthony    时间: 2010-01-27 00:05
原帖由 OwnWaterloo 于 2010-1-26 23:53 发表


你试过没有, 你确定?
23楼的汇编代码你看没有?  哪一个函数会"写"内存???
上面的有vc6、8、9的汇编输出, 你说的VC又是哪个版本?


再者, 只要有一个反例,就可以证明对结构体的返回并不全是采 ...


喂喂,我又没说你,别急啊。。。
我用的VC是VS2003(VC7),此外这个例子只是想反驳某人说的不会产生拷贝而已,注释加在那里也是这个意图。
从反汇编看,O2优化,struct A如果是4字节,用eax返回,8字节用eax+edx返回,确实不是用指针,超过8字节的必定是指针。
好吧,我支持一下你的观点。不过用指针没啥不好的,当然编写者自己要清楚一点原理,对于大结构体至少少一次拷贝。
作者: w_anthony    时间: 2010-01-27 00:09
原帖由 老手 于 2010-1-26 23:44 发表


请搞清楚再说.


我想我是得再清楚一点,这里的例子举的不好,struct A太小了,结果就是OwnWaterloo,直接用寄存器了,看不出你所说的“拷贝无法自圆其说论”,改成
struct A
{
     int i, j, g;
};
就行了。
作者: 老手    时间: 2010-01-27 00:12
原帖由 OwnWaterloo 于 2010-1-26 23:59 发表
"以我的代码"并不能证明什么。

看29楼, 只要能够举出一个没有采用隐式指针参数的反例, 就可以说明这个结论是错误的:



结论是 : 并不都是这样。

上面5份代码, 全都是通过寄存器返回的, 没有写 ...



gcc你我编译的结果相差甚远 . 我编译的支持我的观点.

相信你的VC编译结果 : VC上小结构 , 可以不用隐式指针传递.

也就是如何处理与编译器有关.
作者: 老手    时间: 2010-01-27 00:22
原帖由 w_anthony 于 2010-1-27 00:09 发表


我想我是得再清楚一点,这里的例子举的不好,struct A太小了,结果就是OwnWaterloo,直接用寄存器了,看不出你所说的“拷贝无法自圆其说论”,改成
struct A
{
     int i, j, g;
};
就行了。


你这想说明什么?!
作者: OwnWaterloo    时间: 2010-01-27 00:23
标题: 回复 #31 w_anthony 的帖子
我就缺vc7 ……   vc10都有, 只是不常用……
感觉它是一个过度版本, 用户不如vc6广, 对标准支持度又不如vc8以后的版本……

支持谁其实不重要, 就像你说的, 人总是会犯错的。
所以, 我选择支持真理, 即使是在当前认知条件下狭隘的真理。


并且, 使用指针并不一定会产生高效代码。 但使得代码变得难看是肯定的。

例如, 不能很自然的这么写:
point p = make_point(x, y);

而是要分为2步:
point p;
make_point(&p, x, y);

呃, 我觉得后者不自然……  这个是口味问题。



就效率来说:

T make_T( parameter ) {
      return T( arguments ); // 一个构造函数
}

那么:
T v = make_T(arguments );

有可能就是:
T v; // 没有初始化
new (&v)  T(arguments); // 一次初始化

这是具名返回值优化。 可以直接将返回值, 替换为函数中的具名返回对象。
而这个返回值, 在调用前是一个没有初始化的对象 —— 整个调用正好是它的初始化。



如果显示的使用指针(或者引用)
void make_T(T* t, parameter ) {
      *t = T(parameter) ; // 一次构造, 一次拷贝
}

T v; // 传递之前, 还有一次默认构造
make_T(&t, arguments);
无论编译器能对这种代码优化到什么程度, 它的语意和上面是不同的, 语意上始终要多1次默认构造和拷贝。

[ 本帖最后由 OwnWaterloo 于 2010-1-27 00:25 编辑 ]
作者: w_anthony    时间: 2010-01-27 00:27
原帖由 老手 于 2010-1-27 00:22 发表


你这想说明什么?!


你认为是:
void func(struct A* p)
{
&nbsp;&nbsp;&nbsp;&nbsp;p->i = 0;
}


但是实际上是:
void func(struct A* p)
{
&nbsp;&nbsp;&nbsp;&nbsp;struct A a;
&nbsp;&nbsp;&nbsp;&nbsp;a.i = 0;
&nbsp;&nbsp;&nbsp;&nbsp;*p = a;        //拷贝发生在这里

}


所以我试着去代替某人“自圆其说”一下存在“拷贝”这个事件,我是否是“漏洞百出”?
作者: w_anthony    时间: 2010-01-27 00:34
原帖由 OwnWaterloo 于 2010-1-27 00:23 发表
我就缺vc7 ……   vc10都有, 只是不常用……
感觉它是一个过度版本, 用户不如vc6广, 对标准支持度又不如vc8以后的版本……

支持谁其实不重要, 就像你说的, 人总是会犯错的。
所以, 我选择支持真理, ...


T make_T( parameter ) {
      return T( arguments ); // 一个构造函数
}

举这个例子,不是以偏概全么?
这么return确实是没有拷贝过程的,但是一个复杂的函数结果是返回结构体的话,绝大多数不会这么理想,中间会有临时变量定义,再return临时变量。
而且现在说的是结构体,如果是类的话,用return临时变量的写法,构造函数的调用次数也不会少。

[ 本帖最后由 w_anthony 于 2010-1-27 00:35 编辑 ]
作者: OwnWaterloo    时间: 2010-01-27 00:37
标题: 回复 #33 老手 的帖子
原帖由 老手 于 2010-1-27 00:12 发表
相信你的VC编译结果 : VC上小结构 , 可以不用隐式指针传递.

你这话说的   我没必要捏造一个VC的结果出来吧…………


原帖由 老手 于 2010-1-27 00:12 发表
也就是如何处理与编译器有关.


就是这样。 其实上面的gcc是mingw。 依然是没有写内存, 直接寄存器返回。
不过我也确实没有换到ubuntu上去试过这样的代码。 可能那上面会不同。

"显示使用指针传递" 并不一定比直接返回结构并让编译器安插隐式参数来得高效, 这点上我们意见还是相同的。
作者: OwnWaterloo    时间: 2010-01-27 00:48
标题: 回复 #37 w_anthony 的帖子
嗯, 那确实是一个很极端的例子 ……
作者: 老手    时间: 2010-01-27 01:20
标题: 回复 #36 w_anthony 的帖子
目前的结论是,在对待小结构的处理上,各编译器有所不同 ; 大结构用隐式指针传递 .
只要需要 , 完全可以大胆直接返回结构.

至于你说的
void func(struct A* p)
{
    struct A a;
    a.i = 0;
    *p = a;        //拷贝发生在这里

}

即便不是"漏洞百出",也一样以偏盖全了.
作者: 老手    时间: 2010-01-27 01:23
原帖由 w_anthony 于 2010-1-26 23:31 发表
最烦那些一上来就装X的,摆出一付自己高人一等架势的家伙。就算你再牛X,谦虚一点不行么?更何况凡是人,都有出错的可能,给别人点面子,也给自己留条后路。

struct A
{
&nbsp;&nbsp;&nbsp;&nbsp;int i;
...


我还是澄清一下:

发送到:         老手
时间:         2010-1-26 23:57
内容:        
原始短消息: 求教

QUOTE:
原始短消息: 求教



函数返回和a=b再具体的处理上是不一样的.
你还是看帖子吧.

言重之处,请多包涵! 都是来交流的嘛,只要不是人身"母鸡"就好.

这个没事,正好以前理解有误,多谢纠正
作者: OwnWaterloo    时间: 2010-01-27 01:35
标题: 回复 #37 w_anthony 的帖子
终于查到了……

  C++03  12.8  p211

When certain criteria are met, an implementation is allowed to omit the copy construction of a class object,
even if the copy constructor and/or destructor for the object have
side effects. In such cases, the implementation
treats the source and target of the omitted copy operation as simply two different ways of referring to
the same object
, and the destruction of that object occurs at the later of the times when the two objects
would have been destroyed without the optimization.111) This elision of copy operations is permitted in the
following circumstances (which may be combined to eliminate multiple copies):
— in a return statement in a function with a class return type, when the expression is the name of a
non-volatile automatic object with the same cv-unqualified type as the function return type, the copy
operation can be omitted by constructing the automatic object directly into the function’s return value
— when a temporary class object that has not been bound to a reference (12.2) would be copied to a class
object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary
object directly into the target of the omitted copy

[Example:
class Thing {
public:
Thing();
˜Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class Thing:
the copying of the local automatic object t into the temporary object for the return value of function f()
and the copying of that temporary object into object t2. Effectively, the construction of the local object t
can be viewed as directly initializing the global object t2, and that object’s destruction will occur at program
exit. —end example]



T /* temporary*/ make_T(p) {
      T r;                     // 一次默认构造
      calculate(&r, p); // 在r上计算
      return r;             // 这里满足条件1, 所以返回值那个临时对象其实是引用这个local r的另一种方式
// r 将直接在temporary 上构造; 而在r上的计算, 也将被替换为在temporary上的计算
}

T v = /* temporary */ make_T(a);
// 这里满足条件2, temporary 将直接在v上构造; 而在temporary上的计算, 也将被替换为在v上的计算。


所以, 实际上C++实现被允许将上面的调用, 优化为如下形式:
T v;
calculate(&v, a);

总共1次构造默认, 1次计算。


如果改写为:
void make_T(T* t, p) {
      calculate(t, p);
}

并这样调用:
T v;
make_T(&v, a);

也是1次默认, 1次计算。



这样看来, 确实make_T(T* t, p ); 可以保证在任何情况下, 都是1次默认构造, 1次计算。
而 T make_T(p); 需要满足如下条件:

1. 编译器只是被允许这样做。 只有它真的这样做, 才能达到和上面相同的效率。

2. 如果是这样调用:
T v;
v = make_T(a); // 这就不是copy construction, 而是赋值。

语意上, make_T内部将在temporary上进行一次默认构造, 再进行一次计算。
make_T外, v有一次默认构造, 还会从temporary中赋值。

一共是2次默认构造, 1次计算, 1次赋值。

很亏……
作者: OwnWaterloo    时间: 2010-01-27 03:16
标题: 回复 #42 OwnWaterloo 的帖子
以前一些悬疑问题, 终于释怀了……

-------- -------- -------- --------
比如这里:
http://bbs3.chinaunix.net/thread-1621888-1-1.html

是emacsnw牛说中了, 其实是gcc3的一个bug。


-------- -------- -------- --------
还有这里:
http://bbs3.chinaunix.net/thread-1505460-1-1.html


用上面的标准来判断2个调用:

1. A v1 = A();
2. A v2 = A(2, 3);

都满足条件2, 所以被允许直接在v1和v2上构造, 被允许优化为如下形式:
A v1;
A v2(2, 3);


-------- -------- -------- --------
而一些库中出现的"计算式构造函数" —— 就是上面的极端例子 —— 也是有道理的。

-------- -------- -------- 返回值
string cat(char const* s1, char const* s2) {
      return string(s1, s2); // 满足条件1 省略复制
      // 直接在临时对象t上构造, string t(s1, s2);
}

-------- -------- 高效调用
string v = cat("hello ", "c++" ); // 满足条件2, 直接在v上构造临时对象t。 允许被优化为:
string v("hello ", "c++" );

总共1次计算式构造。


-------- -------- 低效调用
string v;  // 默认构造
v = cat("hello ", "world" ); // 赋值

总共构造2次(计算式构造t, 默认构造v), 赋值1次。


-------- -------- -------- 传入参数 (死板设计)
如果设计为:
void cat(string& s, char const* s1, char const* s2) {
      s = string(s1, s2); // 1次计算式构造, 1次赋值
}

调用:
string s;
cat(s, "hello ", "c++" );

总共构造2次(默认构造s, 计算构造t), 赋值1次。

-------- -------- -------- 传入参数
实际上, 那个极端的例子的传入参数版本不应该死板的调用计算式构造函数。
string应该有一个cat成员, 在已构造好的对象上操作。

void cat(string& s, char const* s1, char const* s2) {
      s.cat(s1, s2);
}

调用:
string s;
cat(s, "hello ", "c++" );

总共默认构造s 1次。 cat一次。
而cat可能会分配新的buf, 再丢弃原有buf —— 实际上, 类似于如下设计:

void cat(string& s, char const* s1, char const* s2) {
      string calculator(s1, s2);
      swap(s, calculator);
}

-------- -------- -------- 总结
对可以实现计算式构造函数的类型, 直接返回值, 可以避免1次无用的默认构造 —— 构造, 然后马上丢弃。


-------- -------- -------- --------
上面是按C++03标准分析的, 不知道具体优化情况如何。
C++0x(不太关注……) 添加了move语意, 不知道两种设计又会怎样。

-------- -------- -------- --------
C++真复杂 -_-
作者: 思一克    时间: 2010-01-27 09:01
结构很小的时候不是真实情况。可以优化为通过寄存器了。
大的结构说明问题。

一般是用一个隐指针参数告诉函数站上的临时结构地址。返回结构的函数完成memcpy.
主函数中有额外的结构赋值(不是调用那个),还有memcpy发生。
调用的赋值主函数中不用memcpy了,因为函数中做了。

不同编译和平台实现可能有所差异。
作者: w_anthony    时间: 2010-01-27 09:17
标题: 回复 #43 OwnWaterloo 的帖子
原帖由 OwnWaterloo 于 2010-1-27 01:35 发表
T /* temporary*/ make_T(p) {
      T r;                     // 一次默认构造
      calculate(&r, p); // 在r上计算
      return r;             // 这里满足条件1, 所以返回值那个临时对象其实是引用这个local r的另一种方式
// r 将直接在temporary 上构造; 而在r上的计算, 也将被替换为在temporary上的计算
}


测试了一下,VC7没遵守这规则,不管开不开优化这里都会有Copy Construct,而MinGW不管开不开优化则都没有Copy Construct。
对于不超过8字节的结构体,就算存在复制也没多少时间,直接return结构体也挺好的。一般来说可以直接return 构造函数()的理想情况是比较少的 ,如果return的结构体比较大,那就得依赖编译器的行为了。
作者: llsshh    时间: 2010-01-27 10:40
标题: 回复 #44 思一克 的帖子
您说绕口令那,看不明白啊。
搭配个函数说吧。
作者: Alligator27    时间: 2010-01-27 11:47
函数调用协议是CPU的ABI规定的,没有第二种可能,否则不能实现binary级的兼容。

(1)返回C结构体,如果指定的返回寄存器能够容纳,即由寄存器返回(传值),否则调用函数负责准备空间,并把它的地址隐含地传入被调用函数,返回寄存器返回该地址(传引用)。

(2)返回C++结构体,如果是POD(类似于C结构体),同上;如果非POD,即使只有一个字节,总是传引用,而且会调用拷贝函数。

调用函数与被调用函数之内怎么处理返回值,可以由具体的编译器的实现(或选项)决定。
作者: 思一克    时间: 2010-01-27 11:56
原帖由 llsshh 于 2010-1-27 10:40 发表
您说绕口令那,看不明白啊。
搭配个函数说吧。


  1. //thisfile.c
  2. #include <stdio.h>

  3. struct A {
  4.     char ch[4*1024];
  5. };

  6. char *stkl, *stkh;
  7. struct A f(int i)
  8. {
  9. int k, *kp;
  10. struct A b;
  11. int tmp;

  12.     b.ch[0] = 'a';
  13.     printf("b at %p - %p\n", &b, &b+1);
  14.     printf("i = %i in %p %p\n", i, &i, *(&i + 1));
  15.     kp = &k;
  16.     for(k = 0; k < 10; k++) printf("%d %p %p\n", k, kp, *kp++);

  17.     stkl = &tmp;
  18.     printf("tmp at %p stack size = %d\n", &tmp, stkh - stkl);
  19.     return b;
  20. }

  21. main()
  22. {
  23. struct A c;
  24.     stkh = &c + 1;
  25.     //c = f(123);
  26.     f(123);
  27.     printf("c = %c start at %p\n", c.ch[0], &c);
  28. }

复制代码


你编译用gcc -S -o asm thisfile.c
可以看见asm中的汇编。
作者: hzsjx    时间: 2010-01-27 12:40
可以,但一般不会这么做的
作者: alphayeah    时间: 2010-01-27 12:58
原帖由 思一克 于 2010-1-27 11:56 发表



//thisfile.c
#include

struct A {
    char ch[4*1024];
};

char *stkl, *stkh;
struct A f(int i)
{
int k, *kp;
struct A b;
int tmp;

    b.ch[0] = 'a';
    printf("b at %p -  ...


没什么好讨论的,就是指针的用法。
不过,很讨厌这种直接返回结构体的,很明显,会增加复制操作。
编译的优化就不说了,我只信自己。
作者: 思一克    时间: 2010-01-27 13:09
原帖由 alphayeah 于 2010-1-27 12:58 发表


没什么好讨论的,就是指针的用法。
不过,很讨厌这种直接返回结构体的,很明显,会增加复制操作。
编译的优化就不说了,我只信自己。


是的。
返回大结构是愚蠢行为。
作者: windfaddy    时间: 2010-01-27 13:35
返回结构体指针比较快速。
作者: xxw19840406    时间: 2010-01-27 14:25
学习了
一直都是用指针的
作者: pmerofc    时间: 2010-01-27 17:19
提示: 作者被禁止或删除 内容自动屏蔽
作者: yazi0127    时间: 2010-01-27 17:40
我是LZ。这个问题是在测试一段代码中发现的,developer说C里面不允许返回结构体,说我的case设计的有问题,心里不服气,故上论坛中一探究竟。
首先感谢大家的认真分析,热情回贴。总结了一下:

原帖由 思一克 于 2010-1-27 09:01 发表
结构很小的时候不是真实情况。可以优化为通过寄存器了。
大的结构说明问题。

一般是用一个隐指针参数告诉函数站上的临时结构地址。返回结构的函数完成memcpy.
主函数中有额外的结构赋值(不是调用那个), ...

我觉得这个说的和我理解的差不多。谢谢!
作者: N_Line    时间: 2010-01-27 17:43
学习了,学术就要这样,很喜欢这里的学术氛围
作者: pmerofc    时间: 2010-01-27 18:09
提示: 作者被禁止或删除 内容自动屏蔽
作者: OwnWaterloo    时间: 2010-01-28 00:55
标题: 回复 #45 w_anthony 的帖子
原帖由 w_anthony 于 2010-1-27 09:17 发表
测试了一下,VC7没遵守这规则,不管开不开优化这里都会有Copy Construct,而MinGW不管开不开优化则都没有Copy Construct。

嘿嘿, 换VC8吧~

看这个:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx

Named Return Value Optimization in Visual C++ 2005

Summary: Shows how the Visual C++ compiler eliminates redundant Copy constructor and Destructor calls in various situations. (12 printed pages)


原帖由 w_anthony 于 2010-1-27 09:17 发表
一般来说可以直接return 构造函数()的理想情况是比较少的


少和多不好定义。 举一些例子吧。  STL里面返回值的函数包括(但不限于):
string的substr是返回值的。
stringstream的 一个str重载。
valarray 的 许多成员。

我随便翻了翻gcc2.9.5(因为我觉得可能对低版本的编译器更需要这些比较恶心的技巧) 和gcc3.4.5所带的stl。
几乎全都使用的"计算式构造函数"。

如果使用C++0x的move语意能够实现, 这些成员就可以依然高效的返回值, 而且不必使用这种比较恶心的技巧。
作者: OwnWaterloo    时间: 2010-01-28 00:58
标题: 回复 #51 思一克 的帖子
原帖由 思一克 于 2010-1-27 13:09 发表
是的。
返回大结构是愚蠢行为。


能详细解释一下吗? 为什么愚蠢?
作者: OwnWaterloo    时间: 2010-01-28 01:00
标题: 回复 #57 pmerofc 的帖子
这不奇怪。 返回结构体是C在标准化时加入的特性。 在那之前, 好像确实存在不可以的编译器。
这个特性的加入, 还引起了不少程序员的反感

btw: 能说说你的看法么?
作者: pmerofc    时间: 2010-01-28 01:24
提示: 作者被禁止或删除 内容自动屏蔽
作者: OwnWaterloo    时间: 2010-01-28 01:36
标题: 回复 #61 pmerofc 的帖子
经常看到类似这样的代码: (可能写错了 大致就这么个意思   )
int f(a, b)
      int a,
      int b
{
      ...
}
怎么说呢, 当历史车轮前进的同时, 总是有守旧派的

说到自然的表达思想,有句话但不记得出处了: 代码是给人看的, 只是顺便让机器执行
作者: windaoo    时间: 2010-01-28 01:51
原帖由 OwnWaterloo 于 2010-1-28 01:36 发表
经常看到类似这样的代码: (可能写错了 大致就这么个意思   )
int f(a, b)
      int a,
      int b
{
      ...
}
怎么说呢, 当历史车轮前进的同时, 总是有守旧派的

说到 ...


也许不是守旧,而是为了能让古老的编译器也能编译通过这代码?
作者: pmerofc    时间: 2010-01-28 01:52
提示: 作者被禁止或删除 内容自动屏蔽
作者: pmerofc    时间: 2010-01-28 01:55
提示: 作者被禁止或删除 内容自动屏蔽
作者: w_anthony    时间: 2010-01-28 10:34
原帖由 OwnWaterloo 于 2010-1-28 00:55 发表

嘿嘿, 换VC8吧~

看这个:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx

Named Return Value Optimization in Visual C++ 2005

Summary: Shows how the Visual C++ compiler eli ...


都能这么优化了,那也没啥好诟病的了,直接返回大结构的唯一软肋现在就剩一个了:
A func(int param);
A a = func(0);
doSomething(&a);
a = func(1);           //软肋
doSomething(&a);

虽然可以再分配一个A,不过有时候也存在就需要往指定内存填数据的情况。

[ 本帖最后由 w_anthony 于 2010-1-28 10:35 编辑 ]
作者: versaariel    时间: 2010-01-28 10:38
学习下
作者: daviescai    时间: 2010-01-28 15:13
ABI
作者: 老手    时间: 2010-01-28 16:03
原帖由 daviescai 于 2010-1-28 15:13 发表
ABI


是的. 可能就是二进制兼容的需要导致了gcc在windows和linux上不同的编译结果 .
作者: OwnWaterloo    时间: 2010-01-28 17:15
标题: 回复 #64 pmerofc 的帖子
我也不是经常去翻标准的……  做编译器的人才是标准专家
只是有时候希望代码的行为能够有某种保证时, 才去翻翻看有没有这种保证。
比如最近翻到3个比较受用的保证:
1. struct 中可以有padding, 但绝对不会出现在第1个field之前。
2. union中所有field的offset都是0 。
3. ==, !=, < ... 这些操作符的结果都是0或者1 。


我英文也很烂……   6级都没过……
那段话是出自这里吧?

c99 6.5 Expressions p67
2 Between the previous and next sequence point an object shall have its stored value
modified at most once by the evaluation of an expression. Furthermore, the prior value
shall be read only to determine the value to be stored.70)

...

70) This paragraph renders undefined statement expressions such as
i = ++i + 1;
a[i++] = i;
while allowing
i = i + 1;
a[i ] = i;


没看懂……  
我还搜了一些其他资料, 比如这个: http://www.embedded.com/story/OEG20020625S0041

依然没看懂……  觉得有些歧义。 也许是我理解错了……



但是在附录中翻到一个对应的条款, 好像更容易理解一些:
c99 J.2 Undefined behavior p491
— Between two sequence points, an object is modified more than once, or is modified
and the prior value is read other than to determine the value to be stored (6.5).


那个prior真的很关键……  指的是:
1. modify之前
2. sequence point之前

如果是2, 那基本就说得通了。 如果是1……  还是不懂……
然后就困了睡了……


出门寻求场外援助去了
晚上回来看能不能将这这个词理解准确一些……



ident是什么?
作者: OwnWaterloo    时间: 2010-01-28 17:22
标题: 回复 #66 w_anthony 的帖子
你看这么理解可行不?

1. 如果功能已经确定, 就是"修改一个已经存在的对象"
那没得说, 肯定是传入该对象的引用或者指针, 而不是返回一个新的值。

比如 replace

2. 如果功能被确定为 "返回一个新对象"
在这种情况下, 将功能改变为"默认创建, 然后修改", 肯定是有损清晰自然的表达。
而且效率也不见得会高。

比如 substr


3. 如果某个需求既可以被设计为1, 又可以被设计为2
这……   就得仔细研究了……
valarray采用的是2 , 而不是1 。 而且是故意这么设计的。 理由我忘了……

[ 本帖最后由 OwnWaterloo 于 2010-1-28 17:24 编辑 ]
作者: pmerofc    时间: 2010-01-28 17:54
提示: 作者被禁止或删除 内容自动屏蔽
作者: OwnWaterloo    时间: 2010-01-28 19:23
标题: 回复 #72 pmerofc 的帖子
ident好像没有什么特殊含义, 就是为下面要描述的那个东西起一个名字
根据ident是斜体瞎猜的……

类似的就像:
there is a function f ...
f balabala ...

有一个函数f ...
f 啥啥...

let's store the value to a local variable v ...
v balabala ...
将这个值存放到一个局部变量v中 ...
v 啥啥...



呃…… 那个prior……  再研究研究……


不用客气
将这个问题彻底搞清楚对我也有好处
比如以前只能"大致觉得"某些(面试)题很sb, 但其实又很心虚 —— 万一误会它了呢?
彻底弄明白之后, 就可以肯定它们中的一些就是sb
作者: OwnWaterloo    时间: 2010-01-28 21:53
标题: 回复 #70 OwnWaterloo 的帖子
场外求助失败     无法得知prior 到底指的什么……
根据给出的例子, 可能是指sequence point之前。 但这种推测肯定是不严谨的, 是由结论去推测过程……

期待大牛了……
作者: w_anthony    时间: 2010-01-28 21:54
原帖由 OwnWaterloo 于 2010-1-28 17:22 发表
你看这么理解可行不?

1. 如果功能已经确定, 就是"修改一个已经存在的对象"
那没得说, 肯定是传入该对象的引用或者指针, 而不是返回一个新的值。

比如 replace

2. 如果功能被确定为 "返回一个新对 ...


差不多,不过对最后你说的保留意见。
另外如果是DLL导出函数,还是要用1方式,毕竟不同的编译器行为不确定,用2的话MinGW的dll给VC调用就可能会出问题。
作者: OwnWaterloo    时间: 2010-01-28 22:22
标题: 回复 #75 w_anthony 的帖子
再换一个角度吧……

比如就是string.replace。 既可以提供返回值的, 也可以提供在一个对象上修改的版本。
(不写模板了, 实际上是个模板)

class string {
public:
      string replace( ... );
static void replace( string& s, ... );
};

那么, 想替换, 并且得到一个新的string, 就可以调用返回值的。
想在既有对象上修改的, 就可以调用传入引用的。

这是2种不同的功能。
当需要一个新对象时 —— 而不管某个特定的函数该如何设计了, 而是从用户的需要来说 —— 返回值可能就会比绕弯去使用引用要快。
当需要在原对象上修改时, 那肯定不能设计为返回值。





"另外如果是DLL导出函数,还是要用1方式,毕竟不同的编译器行为不确定,用2的话MinGW的dll给VC调用就可能会出问题。"
这不是"应该返回值时将其改为创建然后修改"的理由。

这是设计的另一个方面了 —— 是否需要隐藏对象的表示。
如果确实需要, 是不可以创建对象的值, 而只能使用引用的。




这么说吧, 前面一个设计, 可以通过一个问题来得到答案 "你需要获得一个新对象吗?"
如果是, 就返回值; 否则就采用参数。

后面一个设计, 也可以通过一个问题来得到答案 "你要限制按值语意使用对象吗?"
如果是, 连直接创建对象都不可以, 只能通过引用或者指针来创建, 使用, 销毁对象。
在这种情况下, 讨论前一个问题就是无意义的了 —— 本来就不存在(或者不可见)对象的值, 只有引用或指针。
作者: pmerofc    时间: 2010-01-28 22:24
提示: 作者被禁止或删除 内容自动屏蔽
作者: OwnWaterloo    时间: 2010-01-28 22:52
标题: 回复 #77 pmerofc 的帖子
琢磨出来后麻烦分享一下哦  先谢谢了
作者: w_anthony    时间: 2010-01-29 08:47
原帖由 OwnWaterloo 于 2010-1-28 22:22 发表
再换一个角度吧……

比如就是string.replace。 既可以提供返回值的, 也可以提供在一个对象上修改的版本。
(不写模板了, 实际上是个模板)

class string {
public:
      string replace( ... );
s ...


你想太多了,举个例子Windows api的BOOL GetIconInfo(HICON hIcon, ICONINFO* piconinfo),它可不是ICONINFO GetIconInfo(HICON hIcon)。
或许你是担心传进去的是一个类对象,会在函数内部作new或malloc,但却在外部delete或free,而由于内存分配和释放不在同一模块中导致程序挂掉,不过这又是另外的问题。
作者: gigabyte    时间: 2010-01-29 15:06
可以,最好用指针
作者: xiaonanln    时间: 2010-01-30 00:15
答案只有2个字的问题你们都能讨论的这么high啊
作者: pmerofc    时间: 2010-01-30 00:23
提示: 作者被禁止或删除 内容自动屏蔽
作者: thankcs_cu    时间: 2010-01-30 22:01
楼主可以这样做
作者: thankcs_cu    时间: 2010-01-30 22:03
楼主你可以这样嘛
int func(FUNCTION_STRUCT *test)
{
    strcpy(test->fuck,"日本AV女郎");
}
作者: coneagoe    时间: 2010-01-30 23:39
太长,没时间看完。
作者: sykp241095    时间: 2010-01-31 12:13
需要拷贝,我会用指针
作者: vairkey    时间: 2010-02-04 10:21
有点含糊了。。。
作者: vbs100    时间: 2010-02-04 11:20
返回结构体是有潜在问题的 结构体内指针指向的内容肯定不会被复制
作者: ChianXu    时间: 2012-05-01 16:51
各位都是牛人啊。
作者: ChianXu    时间: 2012-05-01 16:54
记得好像是先把要返回的值复制到一个临时返回存储单元中,然后在主函数中接收函数的返回值时从临时返回存储单元中在复制过来。
C++中返回引用的话好象就没有这么复制了,返回引用直接就是返回一个原变量的别名,接受的时候直接从该别名复制过来。中间少了一趟复制过程。

前面的都是大牛啊。极具专业精神。
作者: ChianXu    时间: 2012-05-01 16:54
回复 84# thankcs_cu

我靠,这都行。
   
作者: ecloud    时间: 2012-05-02 14:26
pmerofc 发表于 2010-01-28 01:55
忘了是谁的名言了,“妻不如新,妾不如旧”
借用一下
代码可以旧

是“衣不如新,人不如旧”
你这货就知道妻妾
作者: ecloud    时间: 2012-05-02 14:34
vbs100 发表于 2010-02-04 11:20
返回结构体是有潜在问题的 结构体内指针指向的内容肯定不会被复制

这篇帖子一大堆人在闲扯淡,就你这句话说道点子上了,这个问题才是比那些什么多用几点内存,多要几轮CPU运行更大的问题,完全有可能影响程序设计呢
所以我对学生们的建议是,所有,所有的指针变量要求用p_开头,以便于自己搞清楚状况,结构体内指针使用一定要谨慎
这种指针内容不被复制的特点,是双刃剑,有时候可以利用,有时候一定要小心
作者: KanonInD    时间: 2012-05-02 14:57
vbs100 发表于 2010-02-04 11:20
返回结构体是有潜在问题的 结构体内指针指向的内容肯定不会被复制

于是就有了深复制和浅复制。
作者: OwnWaterloo    时间: 2012-05-02 15:10
回复 93# ecloud

"返回结构体,结构体当中非值语意的域需要考虑其所有权" —— 对此:
1. 我怀疑对参与此楼的"一大堆""在闲扯淡"的人来说,都是显而易见的,不需要特别关注的implied的常识。
2. 也许就对你、以及你的学生们不那么显而易见,是需要特殊提醒的,是说到点子上了的,是个设计的问题。
作者: wait_rabbit    时间: 2012-05-02 16:17
ecloud 发表于 2012-05-02 14:26
是“衣不如新,人不如旧”
你这货就知道妻妾


茕茕白。东走西。衣不如新、人不如
作者: d19890104    时间: 2012-05-03 16:34
觉得要修改一个结构体,在进函数的时候,应该就传个指针进去,出来的时候再传个指针出来,多省事,就没有那么多麻烦了
作者: Q__Q11    时间: 2015-05-04 21:08
c语言函数是什么?
alex999.com/c_language_function.html
作者: jd808    时间: 2015-05-05 10:53
d19890104 发表于 2012-05-03 16:34
觉得要修改一个结构体,在进函数的时候,应该就传个指针进去,出来的时候再传个指针出来,多省事,就没有那 ...

你传出来是return吗?为啥要return呢?指针进去不是直接在指针上修改嘛?
作者: mymtom_cu    时间: 2015-06-03 10:28
很少这么用的呀,通常返回结构体的指针。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2