话说这C中的动态获取二维数组好比艺术作品中的爱情(好烂的比喻~~),都是永恒而难缠的主题。假期写并行的作业,又碰到了这个问题,依然绞尽脑汁。
其实二维数组理解起来是很容易的,一维的是void *,二维自然就是void **,高维以此类推。然而多了一个和数个*好比一个“不可逾越的障壁”。不过直接申请**,初始化很难并且退回也很难。上次遇到的时候就直接malloc了一个width * height大小的区域,然后写一个取值的宏。看起来很丑陋。这次决定做个了断。搜到台湾一个技术论坛,找到了这个终极方案,真的很妙~
原帖地址:http://y120062140.win.aplus.net/pc2020v5/forum/showSameTitleN.asp?board_pc2020=c&id=34046&keyword=
原帖讨论了至少2种方案,不再赘述。最好的方案是“ma_hty(白老鼠(Gary))”的那个。程序如下:
void
**
malloc2d(
int
w,
int
h,
int
size)
{
int j;
int rowSize = w * size;
int indexSize = h * sizeof(void *);
void **a = (void **) malloc(indexSize + h * rowSize);
char *dataStart = (char *) a + indexSize;
for(j = 0; j < h; j++)
a[j] = dataStart + j * rowSize;
return a;
}
参数w,h是所申请二维数组的列数和行数,size是数组单元的字节数。比如,申请一个4*5的int型的二维数组,使用:
int **m = (int **) malloc2d(5, 5, sizeof(int));
直接使用m[x][y]即可以引用x行y列的值。
退回时,直接使用free(m)即可。
跟白老鼠开始的程序相比,改了一些地方,返值改成void **,(char *)(a + h)改成了(char *) a + h。加进了一些循环外的计算。
解释一下~就好像原帖里面cromayen2000所讲的,动态申请二维数组通常需要一个index区域和一个数据区域。index区域保存每一行数据区域的首地址,数据区域存放实际的值。m的地址是index区域的首地址。
这样,对二维数组中x行y列元素的引用m[x][y],实际上是一个两步的运算。
首先,m是一个int**的元素。m[x]是*(m + x),即index区域,第x个元素内的值,这个值就是数据区域第x行的起始地址。*(m + x)的类型是int*。
之后,寻找第y列的值。就是*(*(m + x) + y)。正好对int**进行两次解除引用。得到一个int。而写成中括号的表示法,m[x][y] === *(*(m + x) + y)。
白老鼠的函数把index和数据区域连在了一起。
+====+
+--+--* |〔0〕
| |----|
+----+--* |〔1〕
| | |----|
| | ~ ~
| | |----|
+------+--* |〔h-1〕
| | | +====+
| | |
| | | 〔0〕 〔w-1〕
| | | +======================+
| | +->| |〔0〕
| | |----------------------|
| +--->| |〔1〕
| |----------------------|
| ~ ~
| |----------------------|
+----->| |〔h-1〕
+======================+
嗯,这个ASCII图画的真是绝妙。佩服台湾的技术人员。
首先,区域的总长度h * sizeof(void *) + h * rowSize。前一个加数就是index区域的长度。即h个void *指针的长度。后面的加数就是数据区的长度,h个rowSize。
数据区的开始也就是开始指针后移整个index区后的地址。即地址dataStart。循环把后面的数据区每一行开始地址连接到前面的index区域。
一个malloc,一个free,一个二维数组,妙哉~
这个方法要比直接栈上申请多一个index区域,思路和申请w * h大小的区域然后算出每一个元素地址根本不一样。
帖子的后面讨论了一下字节对齐的问题。白老鼠应该是藐视了字节对齐的重要性了。稍微弄一下SSE也会有感觉,字节不对齐的话会不见血的死掉的。按照这个函数,只需要保证数据区的字节对齐就可以了。方法是在index和数据区域之间加上padding。白老鼠随手写出来的程序疑似有问题。自己写了一个。
void
**
malloc2d(
int
width,
int
height, size_t size,
int
align)
{
// alloc all spaces: index area, largest padding area and data area
int index_size = sizeof(void *) * height;
int row_size = size * width;
void **p = (void **) malloc(index_size + align + height * row_size);
// compute padding
char *ptr_index_ending = (char *) p + index_size;
int padding = (align - ((int) ptr_index_ending + 1) % align) % align;
// link index area and data area
// starting address of data area
char *ptr_data = ptr_index_ending + 1 + padding;
for (int i = 0; i < height; i++)
{
p[i] = ptr_data;
ptr_data += size * width;
} return p;
}
在申请总大小的时候把最大可能的padding申请出来(就是要对齐的字节数本身),然后链接的时候,数据区的首地址根据整片区域的首地址数值(a的地址),加上index区的大小,适当后移到对齐的字节数上。
测试了一下应该是好使的,嗯。 最后还是赞一下台湾的PG们,可以看到大家的回帖里面重要的概念都附上了wiki的地址,认真劲儿很了不起啊。
其实二维数组理解起来是很容易的,一维的是void *,二维自然就是void **,高维以此类推。然而多了一个和数个*好比一个“不可逾越的障壁”。不过直接申请**,初始化很难并且退回也很难。上次遇到的时候就直接malloc了一个width * height大小的区域,然后写一个取值的宏。看起来很丑陋。这次决定做个了断。搜到台湾一个技术论坛,找到了这个终极方案,真的很妙~
原帖地址:http://y120062140.win.aplus.net/pc2020v5/forum/showSameTitleN.asp?board_pc2020=c&id=34046&keyword=
原帖讨论了至少2种方案,不再赘述。最好的方案是“ma_hty(白老鼠(Gary))”的那个。程序如下:











参数w,h是所申请二维数组的列数和行数,size是数组单元的字节数。比如,申请一个4*5的int型的二维数组,使用:
int **m = (int **) malloc2d(5, 5, sizeof(int));
直接使用m[x][y]即可以引用x行y列的值。
退回时,直接使用free(m)即可。
跟白老鼠开始的程序相比,改了一些地方,返值改成void **,(char *)(a + h)改成了(char *) a + h。加进了一些循环外的计算。
解释一下~就好像原帖里面cromayen2000所讲的,动态申请二维数组通常需要一个index区域和一个数据区域。index区域保存每一行数据区域的首地址,数据区域存放实际的值。m的地址是index区域的首地址。
这样,对二维数组中x行y列元素的引用m[x][y],实际上是一个两步的运算。
首先,m是一个int**的元素。m[x]是*(m + x),即index区域,第x个元素内的值,这个值就是数据区域第x行的起始地址。*(m + x)的类型是int*。
之后,寻找第y列的值。就是*(*(m + x) + y)。正好对int**进行两次解除引用。得到一个int。而写成中括号的表示法,m[x][y] === *(*(m + x) + y)。
白老鼠的函数把index和数据区域连在了一起。
+====+
+--+--* |〔0〕
| |----|
+----+--* |〔1〕
| | |----|
| | ~ ~
| | |----|
+------+--* |〔h-1〕
| | | +====+
| | |
| | | 〔0〕 〔w-1〕
| | | +======================+
| | +->| |〔0〕
| | |----------------------|
| +--->| |〔1〕
| |----------------------|
| ~ ~
| |----------------------|
+----->| |〔h-1〕
+======================+
嗯,这个ASCII图画的真是绝妙。佩服台湾的技术人员。
首先,区域的总长度h * sizeof(void *) + h * rowSize。前一个加数就是index区域的长度。即h个void *指针的长度。后面的加数就是数据区的长度,h个rowSize。
数据区的开始也就是开始指针后移整个index区后的地址。即地址dataStart。循环把后面的数据区每一行开始地址连接到前面的index区域。
一个malloc,一个free,一个二维数组,妙哉~
这个方法要比直接栈上申请多一个index区域,思路和申请w * h大小的区域然后算出每一个元素地址根本不一样。
帖子的后面讨论了一下字节对齐的问题。白老鼠应该是藐视了字节对齐的重要性了。稍微弄一下SSE也会有感觉,字节不对齐的话会不见血的死掉的。按照这个函数,只需要保证数据区的字节对齐就可以了。方法是在index和数据区域之间加上padding。白老鼠随手写出来的程序疑似有问题。自己写了一个。


















在申请总大小的时候把最大可能的padding申请出来(就是要对齐的字节数本身),然后链接的时候,数据区的首地址根据整片区域的首地址数值(a的地址),加上index区的大小,适当后移到对齐的字节数上。
测试了一下应该是好使的,嗯。 最后还是赞一下台湾的PG们,可以看到大家的回帖里面重要的概念都附上了wiki的地址,认真劲儿很了不起啊。