V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tianshilei1992
V2EX  ›  C

二重指针申请和释放空间的正确姿势(C/C++)

  •  
  •   tianshilei1992 · 2015-06-20 19:10:23 +08:00 · 2688 次点击
    这是一个创建于 3234 天前的主题,其中的信息可能已经有所发展或是发生改变。

    现在想申请一个 h × w 的二维矩阵,用二重指针来表示,申请的代码有两种,如下:
    代码1:
    float allocf(int h, int w) {
    float **p = (float
    )malloc(sizeof(float) * h);
    if (!p) {
    cerr << "Memory Allocation Error!" << endl;
    exit(1);
    }
    for (int i = 0; i < h; ++i) {
    p[i] = (float
    )malloc(sizeof(float) * w);
    if (!p[i]) {
    cerr << "Memory Allocation Error!" << endl;
    exit(1);
    }
    }
    return p;
    }
    代码2:
    int alloci(int h, int w) {
    int a = (int)malloc(h * w * sizeof(int));
    if (!a) {
    cout << "allocu fail." << endl;
    exit(0);
    }
    int **p = (int
    )malloc(h * sizeof(int*));
    for (int i = 0; i < h; ++i) {
    p[i] = &a[i * w];
    }
    return p;
    }
    不要在意类型不同,仅仅是示意~~
    显然,释放空间的代码也不同:第一种分配方法需要写一个循环来释放空间,而第二种只需要两条 free 语句即可。
    那么问题来了,这两种方法哪种比较安全?至于效率上的差距,我想很显然第二种的效率比第一种要好。

    34 条回复    2015-06-21 15:21:40 +08:00
    thinkIn
        1
    thinkIn  
       2015-06-20 19:41:32 +08:00 via iPhone
    (float)malloc (int)malloc 什么鬼?
    rshun
        2
    rshun  
       2015-06-20 19:56:26 +08:00   ❤️ 1
    你的第一段代码如果在第2次内存分配错误之后,就直接exit了,没有free.
    而且是c++的语法,为什么要用malloc,不用new?
    zwy100e72
        3
    zwy100e72  
       2015-06-20 20:10:31 +08:00
    大二学生试着回答一下:
    第一种方法申请到的是:
    指针数组,每个指针单独指向一个数组
    第二种方法申请到的是:
    指针数组,指针即为楼主要存储的值
    zwy100e72
        4
    zwy100e72  
       2015-06-20 20:16:58 +08:00   ❤️ 1
    @zwy100e72 补充:
    第二种办法使用了索引的方法。。。
    指针和对应值不是一起申请的,
    值使用的是一维向量的方式存储的,在逻辑上划分为二维矩阵
    指针指向逻辑上的首地址

    ps: 忘了在什么地方看到的:如果没有释放资源直接退出的话,系统会主动回收资源(但是要长期运行的程序必须注意主动释放
    firemiles
        5
    firemiles  
       2015-06-20 20:30:35 +08:00   ❤️ 1
    @zwy100e72 进程控制块会记录申请的资源,进程退出就会释放资源。
    secondwtq
        6
    secondwtq  
       2015-06-20 20:36:07 +08:00
    @thinkIn 这是 Markdown 的锅...
    tianshilei1992
        7
    tianshilei1992  
    OP
       2015-06-20 21:32:32 +08:00
    @rshun 感谢。
    程序结束了以后不会自动释放所有分配的资源吗?
    tianshilei1992
        8
    tianshilei1992  
    OP
       2015-06-20 21:35:32 +08:00
    @thinkIn 唔,估计是 MarkDown 的问题,程序是带指针的。
    代码1:

    代码2:
    tianshilei1992
        9
    tianshilei1992  
    OP
       2015-06-20 21:37:17 +08:00
    @zwy100e72 是的,那从安全的角度考虑,二者都一样?效率的话,显然是第二种写法更高效。
    tianshilei1992
        10
    tianshilei1992  
    OP
       2015-06-20 21:43:05 +08:00
    @rshun 对于非用户自定义类型,new 和 malloc 应该都差不多吧。
    28hua
        11
    28hua  
       2015-06-20 22:06:12 +08:00
    int (*p) [w] = (int (*)[w])malloc(sizeof(int) * h * w);

    p[i][j]...

    free(p);

    这样呢?
    czheo
        12
    czheo  
       2015-06-20 22:27:06 +08:00
    做涉及矩阵变换的时候,2就比1容易得多了。
    lsmgeb89
        13
    lsmgeb89  
       2015-06-20 22:58:39 +08:00   ❤️ 1
    想起了大一的 C 语言作业哈。

    感觉第二种写法的 p 很多余啊,你直接用 a 访问就好啦,把一维当作二维用啊,要 p 干嘛。

    至于安全嘛,不懂。有书专门讨论 C 的安全写法,参考

    http://book.douban.com/subject/4149534/
    tianshilei1992
        14
    tianshilei1992  
    OP
       2015-06-20 23:12:53 +08:00 via iPhone
    @czheo 因为内存空间是连续的?
    tianshilei1992
        15
    tianshilei1992  
    OP
       2015-06-20 23:15:02 +08:00 via iPhone
    @lsmgeb89 那样写起来很麻烦的,因为操作矩阵每次都要写 a*w+x
    lsmgeb89
        16
    lsmgeb89  
       2015-06-20 23:18:21 +08:00
    @tianshilei1992 其实这点麻烦还算可以接受的啦~~
    canautumn
        17
    canautumn  
       2015-06-20 23:23:04 +08:00 via iPad
    没什么安全不安全的,记着free就行了。说实在的C++那么多安全有用的工具放着不用还用这种低级的语法本身就不安全。如果只是C语言作业练习当我没说。
    tianshilei1992
        18
    tianshilei1992  
    OP
       2015-06-21 00:29:41 +08:00 via iPhone
    @canautumn 咦?用 C++ 来声明一个 w*h 的 int 类型二维数组怎么声明?
    billwsy
        19
    billwsy  
       2015-06-21 00:37:46 +08:00 via iPhone   ❤️ 1
    @tianshilei1992 vector of vector? 如果很注重效率的话我倾向于写个类然后重载[]函数
    zwy100e72
        20
    zwy100e72  
       2015-06-21 01:15:52 +08:00   ❤️ 1
    @tianshilei1992 裸指针/数组没有安全性可言。。。别有用心的人构造一个传入参数很有可能就返回了不该返回的东西。。推荐还是用类来实现相关的操作
    从效率上讲,完全连续的空间效率最高,可以利用prefecher预取到cpu cache中,这个角度的话是一维数组模拟二维效率最高(使用类进行包装后,相关的函数可以成为内联函数,兼顾效率和安全性)
    学识粗浅。。各位随便看看(摊手
    czheo
        21
    czheo  
       2015-06-21 01:39:07 +08:00   ❤️ 1
    @tianshilei1992 比如你要transpose一个矩阵,2只要把指针位置调整一下就可以了,1需要重新申请指针的空间。
    Valyrian
        22
    Valyrian  
       2015-06-21 01:57:44 +08:00   ❤️ 1
    一个array就够了啊,第二个完全没必要。
    int a = (int)malloc(h * w * sizeof(int));

    写个访问函数就好了
    int get_element(int row, int col, int* mat) {
    return mat[row * w + col]
    }
    alphonsez
        23
    alphonsez  
       2015-06-21 02:08:50 +08:00   ❤️ 1
    第二种好用,地址空间连续,有诸多好处。而且free起来也方便。取东西速度也快因为只要一次寻址,第二种有两次。所以如你所说,是矩阵的话,显然第一种好。比如你做个矩阵copy,memcpy即可。

    第一种有一个优势,就是第二个维度可以变化。比如a[0]有20个元素,a[1]有30个元素。不过这种已经不是矩阵了。
    alphonsez
        24
    alphonsez  
       2015-06-21 02:13:23 +08:00
    @czheo 不知道你要transpose的话第二个的优势在哪里。第一个的话可以就地transpose, 当然维度需要变一下。如果要保留原有矩阵那当然都是要new的,第二种new的代价也更高。

    ```
    struct Matrix {
    int h, w;
    float * data;
    bool isTransposed;
    };
    ```
    alphonsez
        25
    alphonsez  
       2015-06-21 02:16:01 +08:00
    @czheo 好吧 我想说2是好的…… 我觉得应该睡觉去了……
    canautumn
        26
    canautumn  
       2015-06-21 08:31:50 +08:00   ❤️ 1
    @tianshilei1992 个人意见,C的二维数组能不用就不用。且不提那么多现成的库(boost::matrix等),项目允许的话给gsl写一个c++wrapper效率也很高(gsl内部对矩阵也是一维数组实现的)。非要自己从最底层上实现的话可以按楼下说的用一位数组写成一个类然后重载[]。另外现代C++连new都不提倡了,别说malloc了。用智能指针能解决很多安全性问题。

    所以我不知道你的指导原则是什么,既要写起来方便(不想写访问器)又想从最底层自己实现,那是矛盾的。
    tianshilei1992
        27
    tianshilei1992  
    OP
       2015-06-21 09:21:17 +08:00
    @canautumn 感谢。这个代码的用处就是配现在正在做的 papers,自然能减少对其他库的依赖就减少。其实 OpenCV 里面已经有矩阵类 Mat,但是导师要求不能用 OpenCV(悄悄的说,他连读图都是自己写的代码……),所以什么东西都自己实现了。
    我提问的初衷,一来是想知道什么是标准的写法(我有写代码的洁癖),二来也是想对比一下二者的优劣势。
    tianshilei1992
        28
    tianshilei1992  
    OP
       2015-06-21 09:22:21 +08:00
    @zwy100e72 感谢,我打算就用一个类重载运算符来实现。
    canautumn
        29
    canautumn  
       2015-06-21 10:31:56 +08:00   ❤️ 1
    @tianshilei1992 做paper的话项目不大自己实现挺好,能更适合自己的需求,避免不必要的性能损耗,但也要注意架构的设计。C++其实没什么标准的写法,因为是一个multi-paradigm language。上边的代码比如就属于用C语言的风格写C++,从内存管理的角度安全性没什么区别,都不安全。用类封装内存管理细节能降低部分内存泄露风险。

    再多说两句,类内部如果用C语言风格管理内存,还得注意重载copy constructor等,而使用智能指针管理内存就简单多了,尽量不碰new/delete/malloc/free才是最安全的C++写法。建议看一下C++ Primer 12-14章……
    tianshilei1992
        30
    tianshilei1992  
    OP
       2015-06-21 10:42:07 +08:00
    @canautumn 好的,知道了,谢谢!
    mintist
        31
    mintist  
       2015-06-21 11:51:42 +08:00   ❤️ 1
    第1种在时间效率上是要低一点,但是具有较好的可扩展性,如果你需要在运行时改变大小,甚至是每一个一维数组的大小,代价是比较小的,只需要realloc相应的一维数组即可;但是第二种就需要realloc整片;还有第1种从2维扩展到N维也比较容易喝直观。
    第1种在free上,逻辑更加humanity,由里到外一层一层来就好了,最下面一层是拿来放数据的,高维放的是各个维度的指针。

    如果我来写的,在初期会用最直观的,最易扩展的修改的第1种(虽然C语言最好不要使用2维以上的,但是在实现数学算法初期,我觉得使程序和算法表达一致更加重要点,这也是为什么Matlab喝Python在学术界呗用的最多的原因之一吧,直观),而后续优化可能去用第2种,甚至如果空间充足,并且对时间要求较高(很多时候都是这样的要求,因为第三方的人感受不到空间,但时间就很容易感受到)直接用全局变量固定数组,拿空间换时间。

    FYI
    tianshilei1992
        32
    tianshilei1992  
    OP
       2015-06-21 13:11:54 +08:00
    @mintist 感谢回复。这个主要是用来做数字图像处理的,所以感觉不需要考虑扩展维度的问题~不过根据上面大家的回复,我想还是封装一个类比较合适!
    Matlab 考虑过,但是考虑到执行效率比较低,就放弃了……现在的程序用 C/C++ 写的,跑一遍都接近一个小时了。主要是运算都是相关的,多线程不太好写,仅有几步可以用 OpenMP 来加速……然后 Xcode 6(Clang 3.6)还不支持 OpenMP……
    qian19876025
        33
    qian19876025  
       2015-06-21 14:16:08 +08:00
    为啥不能直接申请 一大段内存然后 直接强制转换一下给二级指针 然后直接使用二级指针
    你这样搞 要申请多次不说 管理的时候也是麻烦 难不成你的每一个子节点的 所使用的空间大小可能改变 还是怎么滴?
    alphonsez
        34
    alphonsez  
       2015-06-21 15:21:40 +08:00
    @qian19876025 一级指针不容易直接cast成二级指针吧……
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1466 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 17:01 · PVG 01:01 · LAX 10:01 · JFK 13:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.