×

Loading...

Topic

This topic has been archived. It cannot be replied.
  • 工作学习 / 学科技术 / 有没有C++高手?请教个问题,先谢谢了。 +1

    有一个函数,需要返回一个比较大的对象。为了避免堆栈溢出,打算在函数中,在堆上产生这个对象,用智能指针shared_ptr指向对象。然后在函数返回的时候,返回这个对象的引用,而不是返回这个对象本身。这样就可以避免占用大量堆栈内存了。在外层函数中,对这个对象取值,然后就抛弃了。

    之所以不返回指向对象的智能指针,是因为外层函数中,要取得对象本身,还要对指针解引。这样,外面使用这个对象的表达式看起来不好理解。

    我网上搜索过,一般函数返回引用的情况,大都是对象在函数外生成,并作为引用输入参数传递给函数。没发现把函数内,堆上产生对象的引用直接返回的情况。虽然我觉得这种情况应该也是合理的。

    我有几个问题:这么做是否合理?有没有更好的办法?还有,堆上分配的内存会不会在外层函数退出时自动释放?

    请不吝赐教。先谢谢。

    • 不是高手,写过几年C++。理论上你的做法合理,shared_ptr 自己释放内存,虽然我没用过,以前为了避免内存泄漏我的原则是谁创建的对象,谁负责释放内存,也就是对象在函数外产生,把指针或作为引用传递给函数
      • 对。使用智能指针的好处,就是基本上不用管堆上对象的释放了。我的理解是,在函数返回的时候,返回对象的引用,于是shared_ptr 的引用+1,局部的shared_ptr失效,引用-1.还是只有一个引用。外层函数退出的时候,外层的引用失效,内存被系统自动收回。 +1
        • 在函数返回的时候,返回对象,返回引用不会引起reference count + 1,再说稍微好点的编译器会警告的
    • C++ 11里有move constructor,classA(classA&&)就是让你实现这种目的的。返回的是moved copy
      • 好的,我研究一下,好像见过这个&&,没研究过。谢谢。老大,还请问一下,我实现的方式是否合理?
    • 现在编程不应该考虑内存大小问题,都64bit了,
      • 还是要考虑的。最终程序在Vxworks上运行。据说Vx堆栈最大只能设到1M。搞大矩阵计算很容易溢出。关键Vxworks的release版还不检查堆栈溢出,程序可能已经崩溃了,用户还不知道。我是在Windows里的debug版调试的时候发现这个问题的。
      • 没听过“内存泄漏”?
        • 嗯。要是泄露了。多少内存都不够用啊😂
          • 现在的Java程序员都不知道这些
            • 他们好幸福🤣🤣
            • 现在的初级Java程序员都不知道这些
              • 高级Java程序员也需要考虑内存泄漏的事情吗?Java没有内存泄漏的可能吧?
                • 当然有。java leak后memory耗尽吗,查起来还很麻烦
                  • 我对Java一窍不通,纯好奇。Java的堆内存不都操作系统统一管理了吗,也有leak的可能吗?我只听说Java可能有的时候因为管理内存的原因速度慢,有点卡
                    • Java的heap是JVM管理,不是OS管理。leak通常是由于静态变量造成的。内存管理造成的速度慢可以选适合的GC。
                      • 对了,Java运行在虚拟机上。慢是听一个前同事说的,他们的手术机器人要切人的。结果控制系统用java写,运行在Windows上,担心实时性不好。我们是C++运行在Vxwork上,倒是没有实时性的问题。
                  • 不太懂中文术语,内存泄漏是指memory leaking吧?记得Java 的设计初衷之一就是淘汰pointer 使用以避免不当使用造成memory leaking,所以搞出个garbage collector thread。Java 其他memory leaking 不可能吧?请教其详
                    • 参见
                      • 谢谢,学习了。
        • 太落伍,大部分c++编译器都有全局垃圾回收,自己释放内存已经是很古老的想法了,
          • 你的意思是用智能指针?
            • 现代编译器clang,g++都有垃圾回收,有个编译开关,
              • 谢谢,学习了。
                • 现在用c/c++的不多了,最大的开源项目是chromium,各个大公司积极为chromium提交代码,chromium里有400多开源项目,2000万行代码,
                  • 我们代码跟设备打交道,对实时性要求比较高,还用C++。连我们的架构师都说C++这种语言该消失了。🤣🤣唉,路越走越窄呀。人家刚毕业的小青年去大厂当厂哥,上来就拿20万。
          • 确实落伍,我写 C++都是十几年前的事了
    • 用动态数组或集合,类似std::array / std::set(在主程序里建,函数调用时以Reference传入即可)。 使用Reference Count 的 +1 / -1 来控制何时释放内存的方法现在不太用了。
      • 谢谢。如果能用引用传进来就简单了。主要是为了代码看起来不是那么冗长,要在函数内部创建,然后返回直接放到计算公式中去。这就麻烦一点。
        • ”函数内部创建“ --- 你这种方法是”埋地雷“,容易引进BUG。只要逻辑条例正确,”冗长“是没问题的。最怕的是一些小技巧Trick,为将来埋了地雷。 +1
          • 老兄说的有道理。主要我这里是一个矩阵求伪逆的函数,里面的公式就是几个矩阵相乘相加,取反。本来公式挺简洁,要是把每一步矩阵运算都分解,看着实在太惨。
            • ”矩阵相乘相加,取反“ --- 这些运算函数都是定长的数据,哪来的要求使用动态数组的事情,把一个简单的事情复杂化了,
              • 确实定长,但是数据太大,栈放不下,所以才有不得不在堆上创建的苦衷。栈要是搞定了,哪需要这么费劲?这个库别人编写的时候,没考虑大矩阵运算的问题。他们算3X3当然没问题。我算45X9的时候,堆栈溢出了。
                • 既然是堆.把指针传来传去就好了.smart pointer没优势.但也可用.你仔细查手册.有些细节可帮你.但atomic operation极大影响性能.但你不在乎
                  • 这部分没有实时性要求。主要是受堆栈容量的限制了。老兄,赶脚你说的有点问题:智能指针经常操作堆内存呐。本来也是为了堆内存的正确释放,才发明了智能指针。
                    • 你正在把智能指针当普通指针用.所以我说你没事找事.另外,堆和堆栈你要清楚.(似乎你是知道的).一个正常计算机的堆栈能放下你的那个数组.估计你放在了第n个线程上了(n>1)
                      • Heap和stack我当然清楚。😂😂我的理解:有了智能指针,裸指针应尽量避免使用。关于堆内存分配的问题,正是需要智能指针的地方。不是线程的问题。是矩阵太大,函数嵌套也比较多。各线程应该有自己的堆栈吧?correct me if I am wrong
                        • 是.大小不一呢
                          • 我这里有个难处,就是堆栈最大只能设到1M。据说是Vxworks系统的限制。我们的代码可以在Windows下初步调试,然后Vxworks硬件上执行。Windows下我把堆栈设到2M的时候,一切正常。缺省的1M栈,就溢出了。实际上,在Vx上,也貌似正常滴运行了。
                            还是觉得有可能有问题。至少在Windows下,如果1M堆栈能顺利运行,心里就有底了。
                            • Vxworks是bsd Unix. Posix标准的话是堆栈大小可调的
                              • 这个我还真不清楚,我们的架构师说堆栈设为1M,已经最大了。没研究过Vxworks,赶脚是Unix like的
                                • 我二十多年前用过vxworks.与此话题无关
                                  • 我QNX还研究过1,2。Vxworks完全没研究过。
                • 按你的描述: 你的一个函数 “算3X3当然没问题。
                  我算45X9的时候,堆栈溢出了“, 按理说矩阵的”相乘相加,取反“,这类函数应该是有一个或两个INOPUT矩阵,和一个放结果的OUTPUT矩阵,这三者都是已安排的存储单元, 计算时也用不到大量临时变量, 怎么会有堆栈溢出的事情发生。我猜:该不是在这个函数中为放结果的OUTPUT矩阵去申请单元,那就是这个函数设计的败笔和漏洞。
                  • 我没说清楚,我是说,算3X3的矩阵求逆,当然没问题,里面用到的转置,求逆函数都是直接返回矩阵对象,值传递,有大量的临时对象。矩阵规模小没问题。我算45X9的矩阵,就扛不住了。
                    • 这个矩阵求反是不是用了cramer方法使用了recursion?太大矩阵肯定爆stack,如果用LU分解法理论上不会爆栈的。
                      • 伪逆,没用递归。主要就是维数太大。
    • 你如果是函数内产生的shared_ptr则在函数退出的时候会把shared_ptr做清除的。除非你有counter>1,你返回reference会得到一个已被回收的错误的东西,到时候无效内存区域引用就是seg fault,不明白为什么要这样做?shared_ptr一般都是返回shared_ptr让counter++的.
      • 谢谢。貌似你说的有道理,正常我应该返回shared_ptr,外面也用一个shared_ptr来接收,然后在取值,这样就不会发生错误。或者干脆像飞云老兄说的,就在外层的函数里在堆上创建,然后,作为引用参数送进来,这样就可以返回引用了。即便公式看起来冗长一些,但不会出错。
        • 在函数内创建shared_ptr是不好的行为啊,你真的要传递引用那也是要在函数之前创建shared_ptr,然后函数结束后用引用传递给下一步,并且要确保"下一步“是简单的,且从shared_ptr创建到引用结束是atomic的,否则一旦出现循环引用,和racing condition等状态就是埋雷。
          • 貌似你说的有理。我这里到没有多线程的问题,这些步骤应该都是atomic操作。还问个问题,这里如果我用unique_ptr创建,然后把引用送到函数里,这样做合理吗?
            • 用smart pointer传出来的话应该用std::unqiue_ptr,除非还要继续传。std::shared_ptr reference_count在这里是无用供,而且还要调用mutex系统call
              • 看来老兄是高手,正好请教:如果我在函数内用unique_ptr 在堆上创建资源,然后函数返回unique_ptr指针。函数外面也用unique_ptr接收,不继续传了。但这样不是用两个unique_ptr指向同一对象吗?不正是unique_ptr想避免的现象吗?可以这样用吗?
                • 老实说.你似乎不太懂.你上面那个也不太懂.还是用最古老的方法吧
                  • 不懂才要学习啊。
                    • 你打算下一步怎么学呢?
                      • 先把C++ primer, efftive C++捋一遍。争取先入门。你有啥好建议没?
                        • 我这人很自私.不会平白无故地教人的.你这两本书感觉太低级了.话说我刚才给了你一个20万potential的面试:)一起努力.
                          我的单位中.好同事一般都是小问题尽量自己努力的.你这个话题好,但是属于可以自己努力的部分
                          • 我这不还没入门呐嘛。自己琢磨不明白就来问呗。。。虽然不会有20万的potential,学而常习之,不亦乐乎?这两天也给人面试呐,发现国内好多号称架构师的人,还不如我呐。🤣🤣有一个带团队在第一线的,声称C++1X从来没接触过。。。雁过拔毛,有啥高级一点的书推荐没?
                  • 我都十多年没有再碰c++了。:P +1
                    • 哈哈哈,我来加拿大应聘过C++程序猿工作,被俄罗斯正牌码农鉴定为纯忽悠,然后OWNER出来,给offer了一个销售职位
                • 你对std::unique_ptr理解不对,unique_ptr只能move,不能assign,不会出现两个变量指向同一个指针的情况
                  • 确实,这部分以前也没用过。就是说只要不是assign。在函数里面和外面,可以用不同的unique_ptr指向同一个资源?然后在外面unique_ptr失效的时候,资源被自动回收?
                    • 因为你对move,rval没概念,这些是c++11后才有的。这贴子上很多人都是不懂装懂,与起被这些回复误导,还不如看看网页
                      • Move constructor 确实没接触过。Rval是右值吧? +1
                      • 别生气,不一定不懂.太费神

                        Cpp reference我看着也费劲.对他而言不一定看得懂
                        • 我不懂,他该高兴😊才对。有啥好生气的?😂😂😂
                    • 如果用unique_ptr, move后原先ptr就失效了,除非你在func里面return还是用move出来。
                      • 你说的有道理。貌似如果我直接return这个unique_ptr,实际上编译器编译的时候,也是编译成move出来。
              • shared_ptr好像不能move成unique_ptr吧。楼主他在函数内做了shared_ptr的,除非他在函数内做unique_ptr然后move出来才行。

          • 咱同事水平就是肉联高端
            • 十多年没有再碰c++了不要刚我,以前shared,weak互相使用可溜了。
              • 老大,shared,weak出来也就十年多一点。你当初学的快呀。
    • 从功能上你可以这么干.性能有可能会受影响.看编译器能不能优化了.否则有可能会产生copy的动作.C十十这方面规则太复杂了.也有move的.要仔细查
      Bye
      • 就是为了避免copy才这么做。可以copy我就不费那劲了。
    • 根据很久以前(智能指针不流行的时候)的原则,谁construct object, 谁destruct它。 所以想当年我一般都是你说的那种:在函数外创建,用引用给函数,然后在函数外删除。
      • 这样做确实比较规范。问题是,现在编程,总会有些负责创建资源的类吧?否则,什么都要自己创建,岂不是很麻烦?有这样的类,就会有类内创建,类外删除的需求。楼上说的move constructor可能就是答案。
        • 对。C++最近进化很频繁。应该有更好的解决方案
    • 搞到现在,我才了解到楼主的问题是:调用一个已存在的函数计算大尺寸矩阵的逆矩阵(例如45X9),结果这个函数由于堆栈溢出而Crash了,问:如何在最大堆栈只能设到1M条件下解决这个问题? 这样讲是不是比楼主讲的更清楚些?
      • 高人哪。你咋一下就说清楚了聂?服了。👍👍👍谢谢😂😂。补充一下,这个函数内部调用求转置,方阵求逆函数,都是直接返回矩阵对象。造成大量栈内存的占用。
    • 是不是可以就简单返回裸指针?由调用函数决定如何使用返回的指针。
      • 当然可以,没有智能指针的时候,都是这么用的。我的理解,有了智能指针,尽量避免使用裸指针。
        • 函数返回的裸指针马上赋值给一个智能指针,这样灵活性比返回智能指针大一点,也可以省去研究 move constructor 了。
          • 这种方法应该可行,我还是不大愿意裸指针和智能指针混用。最后还是用了unique_ptr。正好把move也搞清楚了。
    • 不会C++, 但是觉得"对象在函数外生成,并作为引用输入参数传递给函数"比较常见,而且符合你的要求。
      • 按照ARAII的原则,确实应该尽量如你所说。但我觉得总可以设计一些类或者方法,专门用来分配资源,这样返回指针应该也是合理的。
    • C和C++的总原则,function的参数碰到structure,一律用指针。
      • C可能是这样。C++用引用应该也可以。
    • 哥们要是还在用C++,建议转Unreal游戏编程吧;游戏做好了,是仅次于毒品军火的挣钱买卖
      • Unreal是指什么?虚拟现实游戏吗?以前倒是有个游戏叫Unreal。
        • 是游戏引擎啊,最早的Unreal游戏就是这款引擎做的,市场上大部分3A 3D大作都是Unreal引擎开发的
          • 学习了,谢谢。当吃Unreal游戏好像做的挺牛的。我就玩了一点点。那个 half life 引擎不好吗?后来反恐精英不是用自己的引擎吗?
            • unreal是通用引擎,不过好多大的游戏公司使用自己的游戏引擎,比如Far Cry 系列,用Crytek引擎;微软最新的的帝国时代也用的定制的特殊引擎
              • 明白了,谢谢。我不是靠C++吃饭,但编程也只会C++。还在入门级水平挣扎涅。游戏引擎估计我学起来有难度,什么纹理,渲染,我一窍不通啊。😂😂
    • 汇报一下情况:根据大家的指导,把求伪逆函数内部用到的求转置,单位阵求逆能函数,内部在堆上产生的矩阵用unique_ptr去指。返回类型也是unique_ptr。

      返回的时候,move出来

      return std::move(pointer);

      研究了一下,这里直接 return pointer;貌似也是可以的,实际执行时,因为unique_ptr不能copy,所以编译器按规则,调用了move;当然不如自己直接move语义更清晰。

      我感觉这里面用shared_ptr应该也是可以的,问题是可能造成了不必要的开销。unique_ptr显然更合适。

      现在的结果是,进入求伪逆函数本身不直接堆栈溢出了。当计算到最后,调用矩阵乘法运算符 *= 的时候,又堆栈溢出了。还得研究一下,怎么样进一步节省堆栈空间。我把临时变量都尽量在堆上生成了。还有什么办法涅?

    • 最后来汇报一下进展,搞定了。读了一下重载的矩阵相乘操作符:* 和 *= 的代码。发现他们都调用了有个底层函数Mutiply,这个函数接收相乘矩阵,以及相乘结果的引用为参数,正好是我需要的。我可以在函数外的堆上先生成结果矩阵。

      前一步已经把求伪逆函数内部需要求转置,方阵求逆计算等函数改成在堆上生成计算结果。

      现在再把那个主要的占据堆栈空间的,好几个矩阵相乘的大公式分成几个步骤相乘。每一步不在直接用 * 来计算矩阵相乘。而是先在堆上生成结果矩阵,然后把他传给Mutiply函数做参数。这样就避免了多个矩阵相乘,要生成很多临时矩阵,占用堆栈空间的问题。

      最后终于把问题解决了。现在Windows下使用缺省1M堆栈空间,不再报告堆栈溢出错误了。

      感谢各位大侠拔刀相助,感觉跟大家讨论之后,很多概念清晰多了。谢谢!我得再把C++入门捋一遍,争取早点入门。LOL

      Have a good week end!

    • 最后再更新一下。跟我们的架构师讨论了一下,他也觉得函数返回unique_ptr的举动不靠谱,虽然暂时正常工作,总觉得危险。于是从善如流,改成了函数外,堆上生成对象,然后用reference送到函数里处理的方法。与本坛大佬们的建议相同,这样也符合RAII的原则。再次感谢各位! +1
    • don't return ref to a local shared_ptr obj, return the object itself. the object is small, and there are all kind of optimization in release version, almost no performance compromise.
      • 老大,就是因为要用到大矩阵做局部变量,才造成堆栈溢出。另外,返回大矩阵也不大合适
        • 大个的obj 不适合放在栈上, shared_ptr<> obj 很小,没问题

          {

          std::shared_ptr<BIG_MATRIX> obj = std::make_shared<BIG_MATRIX>( your params to construct BIG_MATRIX);

          ... do your work

          return obj;

          }

    • 给加个一👍我不是高手,但我的做法就是你搜到的,“对象在函数外生成,并作为引用输入参数传递给函数”,道理很简单,谁的屁股谁负责擦,销毁和生成要在同一个地方,要么都在函数内部,要么都在函数外部。
      我这个做法,当年其他程序员和项目经理都是认可的
      • shared_ptr 说:尽管造吧, use it and forget it, 我负责擦屁股