• hello world
    2019-11-26
    没有引用计数,没有拷贝和移动,没有线程安全,没有自定义delete函数,另外想请教老师一些问题.
    1. 全局静态和局部静态的变量是存储在哪个区域?看很多书是静态存储区,但静态存储区又是什么区?堆?
    2. thread local的变量存储在哪个区?因为线程是动态创建的,理解这个变量内存也应该动态分配的,线程结束内存自动释放?难道也是堆?
    3. 类的大小是怎么定的呢?一般都是看类的成员变量占用字节数再根据是否虚类看是否加4字节,但是类里面有很多成员函数,这些成员函数不占空间吗,如果有静态成员变量或者静态成员函数呢?
    谢谢老师!

    作者回复: 其他都对,不过,自定义delete似乎目前没这个必要?

    1. 好问题。静态存储区既不是堆也不是栈,而是……静态的。意思是,它们是在程序编译、链接时完全确定下来的,具有固定的存储位置(暂不考虑某些系统的地址扰乱机制)。堆和栈上的变量则都是动态的,地址无法确定。

    2. thread_local和静态存储区类似,只不过不是整个程序统一一块,而是每个线程单独一块。用法上还是当成全局/静态变量来用,但不共享也就不需要同步了。

    3. 非静态数据成员加上动态类型所需的空间。注意后者不一定是4,而一般是指针的大小,在64位系统上是8字节。还有,要考虑字节对齐的影响。静态数据成员和成员函数都不占个别对象的空间。

     8
     26
  • Milittle
    2019-11-26
    说实话,这个专栏对于我这个经常使用C++来做项目的人来讲,我认为不适合初学者,上车需要有过C++开发经验的。一般的小伙伴可能会有压力哒,但是如果想学,克服心里畏惧,从这个专栏出发可以迅速的深入。很好的专栏。

    作者回复: 谢谢。这个专栏是要求之前学过、用过C++的。没学过的不合适。

     2
     24
  • NEVER SETTLE
    2019-11-26
    学习笔记:

    1、概念
    堆(heap):在内存管理中,指的是动态分配内存的区域。当被分配之后需要手工释放,否则,就会造成内存泄漏。
    C++ 标准里一个相关概念是自由存储区(free store),特指使用 new 和 delete 来分配和释放内存的区域。
    这是堆的一个子集:new 和 delete 操作的区域是 free store,而 malloc 和 free 操作的区域是 heap 。
    但 new 和 delete 通常底层使用 malloc 和 free 来实现,所以 free store 也是 heap。

    栈(stack):在内存管理中,指的是函数调用过程中产生的本地变量和调用数据的区域。

    RAII(Resource Acquisition Is Initialization):C++ 所特有的资源管理方式。
    RAII 依托栈和析构函数,来对所有的资源——包括堆内存在内——进行管理。
    对 RAII 的使用,使得 C++ 不需要垃圾收集方法,也能有效地对内存进行管理。

    2、堆
    C++程序需要牵涉到两个的内存管理器的操作:

    1). 让内存管理器分配一个某个大小的内存块
    分配内存要考虑程序当前已经有多少未分配的内存。
    内存不足时要从操作系统申请新的内存。
    内存充足时,要从可用的内存里取出一块合适大小的内存,并将其标记为已用,然后将其返回给要求内存的代码。

    2). 让内存管理器释放一个之前分配的内存块
    释放内存不只是简单地把内存标记为未使用。
    对于连续未使用的内存块,通常内存管理器需要将其合并成一块,以便可以满足后续的较大内存分配要求。
    目前的编程模式都要求申请的内存块是连续的。

    从堆上申请的内存需要手工释放,但在此过程中,内存可能有碎片化的情况。
    一般情况下不需要开发人员介入。因为内存分配和释放的管理,是内存管理器的任务。
    开发人员只需要正确地使用 new 和 delete,即每个 new 出来的对象都应该用 delete 来释放。

    3、栈
    大部分计算机体系架构中,栈的增长方向是低地址,因而上方意味着低地址。
    任何一个函数,根据架构的约定,只能使用进入函数时栈指针向上部分的栈空间。
    当函数调用另外一个函数时,会把参数也压入栈里,然后把下一行汇编指令的地址压入栈,并跳转到新的函数。
    新的函数进入后,首先做一些必须的保存工作,然后会调整栈指针,分配出本地变量所需的空间,随后执行函数中的代码。
    在执行完毕之后,根据调用者压入栈的地址,返回到调用者未执行的代码中继续执行。

    本地变量所需的内存就在栈上,跟函数执行所需的其他数据在一起。
    当函数执行完成之后,这些内存也就自然而然释放掉了。
    栈上的内存分配,是移动一下栈指针。
    栈上的内存释放,是函数执行结束时移动一下栈指针。
    由于后进先出的执行过程,不可能出现内存碎片。

    每个函数占用的栈空间有个特定的术语,叫做栈帧(stack frame)。
    GCC 和 Clang 的命令行参数中提到 frame 的,如 -fomit-frame-pointer,一般就是指栈帧。

    如果本地变量是简单类型,C++ 里称之为 POD 类型(Plain Old Data)。
    对于有构造和析构函数的非 POD 类型,栈上的内存分配也同样有效。
    只不过 C++ 编译器会在生成代码的合适位置,插入对构造和析构函数的调用。
    编译器会自动调用析构函数,包括在函数执行发生异常的情况。
    在发生异常时对析构函数的调用,还有一个专门的术语,叫栈展开(stack unwinding)。

    在 C++ 里,所有的变量缺省都是值语义。
    引用一个堆上的对象需要使用 * 和 & 。
    对于像智能指针这样的类型,使用 ptr->call() 和 ptr.get(),语法上都是对的,并且 -> 和 . 有着不同的语法作用。
    这种值语义和引用语义的区别,是 C++ 的特点,也是它的复杂性的一个来源。
    展开

    作者回复: 认真记笔记非常好。

    不过,建议笔记还是记关键字和要点,解释文字不用多。否则篇幅跟原文接近就意义不大了。

     2
     14
  • bo
    2019-11-26
    老师您好!工程的时候,具体怎么考虑在栈上分配还是在堆上分配,更合理些?

    作者回复: 凡生命周期超出当前函数的,一般需要用堆(或者使用对象移动传递)。反之,生命周期在当前函数内的,就该用栈。

     3
     14
  • hello world
    2019-11-26
    话说一般delete.后需要把这个变量置成nullptr吗,我有时候这样写,不知道有没有必要

    作者回复: 如果这个变量下面还有用到的地方,这是个好习惯。不过,这个习惯主要还是从C来的。现代C++不推荐一般代码里再使用裸指针和new/delete的。

     5
     6
  • Geek_3f3bcb
    2019-12-01
    看的有点爽

    作者回复: 哈,你是第一个用这个形容词的。😁

    
     4
  • 史鹏飞
    2019-11-29
    老师在shape_wrapper类下边的foo函数调用完后,会把shape析构掉,但如何析构circle呢?

    作者回复: 这就是面向对象里的基本用法了。在面向对象的继承体系了,shape需要有一个虚析构函数。这样如果有一个shape*实际指向circle,在delete这个指针时,调用的是circle的析构函数(当然析构过程中,最后也会再调用shape的析构函数)。

    下面的代码可以展示这个过程:

    #include <stdio.h>

    class shape {
    public:
      virtual ~shape()
      {
        puts("~shape");
      }
    };

    class circle : public shape {
    public:
      ~circle()
      {
        puts("~circle");
      }
    };

    int main()
    {
      shape* ptr = new circle();
      delete ptr;
    }

    结果是:

    ~circle
    ~shape

    
     4
  • yuchen
    2019-11-26
    怕评论中您看不到,在此再问一下,麻烦您啦~
    上个问题回顾:
    对于图2d有疑惑,希望该图绘制中可以标明main函数占用的栈空间范围及其对应的栈帧,同理,对bar和foo也一样。如果将图2d从下到上每行编号为0,1,2,...,7,那么main、bar和foo对应的栈空间占用、栈帧分别是那几行呢?
    您的回答:嗯,问得有道理。我的颜色选取不够好,回头改一下。按一般的栈帧定义,只有 0 属于 main,1–4 属于 bar。5 以上属于 foo。

    首先,非常感谢您的回复~

    然而,看到有人这样问您:“参数42”和“a=43”分别是函数调用的参数和函数局部变量,应该属于同一个栈帧,为什么这里不同?
    您的回答是:同样,实际实现通常就是这个样子的。参数属于调用者而非被调用者,一般也是由调用者来释放——至少一般 x86 的实现是这个样子。

    那么和您这里回答我的就不一致的呢。您这里回答我1-4属于bar,因此,那个人问的问题(“参数42”和“a=43”应该属于同一个栈帧)这句就是对的。另外您说“参数属于调用者而非被调用者”,这里1-4既然属于bar了,那么参数42不就属于了被调用者bar了吗?我理解的是main是调用者,main调用了bar,则bar是被调用者。
    展开

    作者回复: 这里主要牵涉到“栈帧”是如何定义的。虽然“参数属于调用者而非被调用者,一般也是由调用者来释放”概念上没有错,但我当时对“栈帧”的定义想当然了。我后来又查了一下定义(用词要以大家接受的用法为准),发现参数和局部变量应该算作一个栈帧里。也就是说,你们这儿的质疑是有道理的。所以,目前我已经把图修改了,这样应该就都没有疑问了。

    
     4
  • 吴军旗^_^
    2019-11-25
    老师可推荐一下教程吗? 从php转过来的,感觉有点难。

    作者回复: 如果刚开始学的话,这个专栏可能会有点挑战。可以先看一下 C++ 之父的 A Tour of C++,国内出版叫《C++语言导学》(谢谢小猪钱钱同学告知)。

    另外,《C++ Primer》名声很响,但 848 页初学有点厚了。注意不是《C++ Primer Plus》,这本跟前者完全无关,不推荐。

     1
     4
  • LiKui
    2019-12-19
    内存泄漏的原因之二:
    1. 异常或分支导致delete未得到执行
    2.分配和释放不在一个函数里导致的遗漏delete

    作者回复: 是的。

    
     3
  • Jerry银银
    2019-12-07
    老师反复提到,没有学过、用过c++的人不适合学;我的观点稍微有点不同:计算机基础知识扎实,熟悉Java和c,这门课还是蛮适合的。

    计算机基础知识深厚,深入理解堆和栈的区别,知道什么时候用堆内存,什么时候用栈内存,那么,剩下的就是语法了。

    作者回复: 嗯,有点道理。但需要学习能力很强,因为我假设你是懂C++的基本语法的。

     1
     2
  • xm2018
    2019-11-29
    关于演示栈展开的那段程序,如果main函数里面不try catch的话,第二个foo(42) obj的析构函数不会被调用,程序非法退出。这种情况算不算泄漏?

    作者回复: 一般不这么看。异常安全性对系统有很多约定,违反了约定,通常 terminate 会被调用。这种情况下就是不做清理工作的。

    在Windows上,你甚至可以用 catch(...) 捕获指针越界访问(需要 /EHa 编译参数),但前提条件一样是你需要去 catch。

    从另一个角度,程序崩溃时,大部分资源都会被操作系统回收,不会对系统造成问题。我们说泄漏,关注的主要是程序(长时间)运行过程中应该释放而没有释放掉的东西,如内存、文件句柄、锁等等。

    
     2
  • Gerry
    2019-11-27
    栈通常说是向下增长,从高地址到低地址。文中表述是向上增长感觉欠妥。

    作者回复: 因你这句话,我特地又去查了一下,目前看到的图,开口永远是上方。中英文资料都是如此。

    这个词的来源实际上可能是堆盘子。显然,你只能从上面取放盘子……

     2
     2
  • super-ck
    2020-01-23
    您好,有一点不是很清楚,在n为42时为何不是构造函数-throw-析构函数这个顺序,根据上下文,为42时,按一般逻辑应该进判断执行才对

    作者回复: 是这个顺序。但异常抛出后,如果有相应的catch的话,析构完了才会执行到catch。

    
     1
  • Puck
    2020-01-12
    初学C++时,也喜欢尽量使用栈对象,但后来知道还有“爆栈”一说。为了避免爆栈,开始大量使用智能指针写代码。即“对象一般使用堆内存”。
    且当指针作为类数据成员时,多了一种空指针表示未配置该项的作用,故亦多有使用。如接口 setA(const A&),exec()(调用前必须已经setA),在其数据成员实现上,我就喜欢用智能指针去hold一个A对象而不是直接的A对象。这点不知道大家是怎么做的

    作者回复: 看对象多大了。如果对象很大(大数组吗?),那可以把它放堆上;或把里面的大成员放堆上,把对象做成 RAII 管理对象。

    
     1
  • Sochooligan
    2019-12-26
    请教老师两个问题。
    我的环境是macOS+eclipse+gcc9.2:
    编译选项是:-std=c++17
    问题1:ptr->circle(); 报 invalid use of "circle::circle"
    问题2:后面catch (…)里的 operator delete(ptr)报 'ptr' was not declared in this scope
    说明:我是把那段代码放到一个函数中了。
    展开

    作者回复: 我那是示意,构造是不允许这样调用的。

    析构的语法则可用,但你这个错误似乎是没把 ptr 当成参数传进去。

     1
     1
  • 张珂
    2019-12-26
    老师您好,我说一下我对内存切片那里的理解,不知道对不对:
    返回的是个基类指针shape*,但其实指向的是个继承类circle对象。那么在用户程序里,就算用户记得delete这个指针shape*,也会造成circle部分永久残留在内存,从而造成内存泄漏,我理解的对吗?

    作者回复: 不是。返回shape*是没有问题的。返回shape才会造成对象切片。

    可以通过delete一个shape*来删除一个circle对象的。这个是正常的面向对象行为。要求是shape有虚析构函数。

    
     1
  • Sochooligan
    2019-12-12
    请课堂疑问:
    1. “值语义”这个概念是想说明,C++有指针,很多其他语言没有?
    2. ptr.get() 是什么语义?
    3. ptr->是什么语义?

    作者回复: 1. 恰恰相反,大部分语言里都有像指针的东西,只是不叫指针,不能随意转换和做加减法,也就不那么危险。很多语言都没有值语义——传递对象,而不是对象的指针/引用。

    2. 得到一个指针,当然是引用语义。

    3. 不是完整表达式,问题没有意义。

     2
     1
  • 陈英桂
    2019-12-04
    类成员函数中缺少了拷贝构造函数、赋值操作符重载。

    作者回复: 是问题之一。

    
     1
  • 流浪在寂寞古城
    2019-12-03
    作为一个用c++做过一些项目,但是没有深入学习过c++的我(看到java很亲切呀,哈哈),感觉有些压力,RAII里面的这个语法就没怎么接触过。不过我会坚持看完,认真理解。勉励自己吧。这算一个flag了吧,所有小伙伴一起加油,跟大佬学习。

    作者回复: 加油!跟Java区别还是很大的,要注意。

    
     1
我们在线,来聊聊吧