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

2015-06-20 19:10:23 +08:00
 tianshilei1992

现在想申请一个 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 语句即可。
那么问题来了,这两种方法哪种比较安全?至于效率上的差距,我想很显然第二种的效率比第一种要好。

2848 次点击
所在节点    C
34 条回复
czheo
2015-06-21 01:39:07 +08:00
@tianshilei1992 比如你要transpose一个矩阵,2只要把指针位置调整一下就可以了,1需要重新申请指针的空间。
Valyrian
2015-06-21 01:57:44 +08:00
一个array就够了啊,第二个完全没必要。
int a = (int)malloc(h * w * sizeof(int));

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

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

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

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

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

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

FYI
tianshilei1992
2015-06-21 13:11:54 +08:00
@mintist 感谢回复。这个主要是用来做数字图像处理的,所以感觉不需要考虑扩展维度的问题~不过根据上面大家的回复,我想还是封装一个类比较合适!
Matlab 考虑过,但是考虑到执行效率比较低,就放弃了……现在的程序用 C/C++ 写的,跑一遍都接近一个小时了。主要是运算都是相关的,多线程不太好写,仅有几步可以用 OpenMP 来加速……然后 Xcode 6(Clang 3.6)还不支持 OpenMP……
qian19876025
2015-06-21 14:16:08 +08:00
为啥不能直接申请 一大段内存然后 直接强制转换一下给二级指针 然后直接使用二级指针
你这样搞 要申请多次不说 管理的时候也是麻烦 难不成你的每一个子节点的 所使用的空间大小可能改变 还是怎么滴?
alphonsez
2015-06-21 15:21:40 +08:00
@qian19876025 一级指针不容易直接cast成二级指针吧……

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/200062

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX