免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: yazi0127
打印 上一主题 下一主题

[C] 请教:C语言函数的返回值类型能否为结构体? [复制链接]

论坛徽章:
0
41 [报告]
发表于 2010-01-27 01:23 |只看该作者
原帖由 w_anthony 于 2010-1-26 23:31 发表
最烦那些一上来就装X的,摆出一付自己高人一等架势的家伙。就算你再牛X,谦虚一点不行么?更何况凡是人,都有出错的可能,给别人点面子,也给自己留条后路。

struct A
{
    int i;
...


我还是澄清一下:

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

QUOTE:
原始短消息: 求教



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

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

这个没事,正好以前理解有误,多谢纠正

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
42 [报告]
发表于 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次赋值。

很亏……

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
43 [报告]
发表于 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++真复杂 -_-

论坛徽章:
0
44 [报告]
发表于 2010-01-27 09:01 |只看该作者
结构很小的时候不是真实情况。可以优化为通过寄存器了。
大的结构说明问题。

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

不同编译和平台实现可能有所差异。

论坛徽章:
9
摩羯座
日期:2013-08-15 15:18:48狮子座
日期:2013-09-12 18:07:47金牛座
日期:2013-09-16 13:23:09辰龙
日期:2013-10-09 09:03:27白羊座
日期:2013-10-17 13:32:44子鼠
日期:2014-04-23 15:09:38戌狗
日期:2014-09-17 11:37:542015年亚洲杯之韩国
日期:2015-03-26 10:16:442015亚冠之武里南联
日期:2015-08-18 14:55:52
45 [报告]
发表于 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的结构体比较大,那就得依赖编译器的行为了。

论坛徽章:
0
46 [报告]
发表于 2010-01-27 10:40 |只看该作者

回复 #44 思一克 的帖子

您说绕口令那,看不明白啊。
搭配个函数说吧。

论坛徽章:
0
47 [报告]
发表于 2010-01-27 11:47 |只看该作者
函数调用协议是CPU的ABI规定的,没有第二种可能,否则不能实现binary级的兼容。

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

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

调用函数与被调用函数之内怎么处理返回值,可以由具体的编译器的实现(或选项)决定。

论坛徽章:
0
48 [报告]
发表于 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中的汇编。

论坛徽章:
1
2017金鸡报晓
日期:2017-02-08 10:33:21
49 [报告]
发表于 2010-01-27 12:40 |只看该作者
可以,但一般不会这么做的

论坛徽章:
0
50 [报告]
发表于 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 -  ...


没什么好讨论的,就是指针的用法。
不过,很讨厌这种直接返回结构体的,很明显,会增加复制操作。
编译的优化就不说了,我只信自己。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP