在C语言中,数组是经常被用到的重要数据类型,但在实际使用时,往往有很多工程师会出现各种各样的问题,如内存越界、错误的访问、初始化不当等。这其中有很大一个原因是没有彻底理解数组的存储机制,出现了一些非法地址或者非预期元素的访问和引用。因此,今天就来详细讲一下在C语言中,数组到底是怎么存储的。
首先我们来看一个一维数组:
int array[10] = {0};这是最简单的数组,其内存结构也是最容易理解的,编译器会在内存中划出一段连续的空间用于存储这个数组的元素,并且对于 int 类型来说,每个元素占用的大小为 4 字节。因此,其内存排列如下:
我们可以用下面一段代码验证一下:
#include
jay@jaylinuxlenovo:~/test$ ./test2 array[0] - 0x7ffc729ca960 - 0array[1] - 0x7ffc729ca964 - 4array[2] - 0x7ffc729ca968 - 8array[3] - 0x7ffc729ca96c - 12array[4] - 0x7ffc729ca970 - 16array[5] - 0x7ffc729ca974 - 20array[6] - 0x7ffc729ca978 - 24array[7] - 0x7ffc729ca97c - 28array[8] - 0x7ffc729ca980 - 32array[9] - 0x7ffc729ca984 - 36可以看到输出结果与我们分析的一致,array 数组在内存中处于连续排列,其中下标0为低地址,随着下标的增大,内存地址增大,每次增大的步长为一个元素所占的大小,在这里就是整形大小4个字节。
当然,上面只是一种最简单的情况,相信无须讲解大家都能理解。那么接下来上正题:
多维数组在内存中的排布是什么样的呢?
实践是检验真理的唯一标准,我们直接来写一段代码测试一下:
#include
测试代码中我们以行为主序,依次打印出每一行中每一列的元素信息,到行结尾后再次从下一行开始,直到结束。可以看到运行结果如下:
jay@jaylinuxlenovo:~/test$ ./test2 array[0][0] - 0x7fff25495070 - 0array[0][1] - 0x7fff25495074 - 4array[0][2] - 0x7fff25495078 - 8array[1][0] - 0x7fff2549507c - 12array[1][1] - 0x7fff25495080 - 16array[1][2] - 0x7fff25495084 - 20array[2][0] - 0x7fff25495088 - 24array[2][1] - 0x7fff2549508c - 28array[2][2] - 0x7fff25495090 - 32array[3][0] - 0x7fff25495094 - 36array[3][1] - 0x7fff25495098 - 40array[3][2] - 0x7fff2549509c - 44很巧,我们选用的打印顺序竟然就是数组内存中排列的顺序!可以看到这里的内存递增方式和一开始的一维数组一样,是按照一个整形元素的大小递增的。当然为了严谨起见,我们调换一下程序中的打印顺序,按列为主序打印:
#include
jay@jaylinuxlenovo:~/test$ ./test2 array[0][0] - 0x7ffccc482230 - 0array[1][0] - 0x7ffccc48223c - 12array[2][0] - 0x7ffccc482248 - 24array[0][1] - 0x7ffccc482234 - 4array[1][1] - 0x7ffccc482240 - 16array[2][1] - 0x7ffccc48224c - 28array[0][2] - 0x7ffccc482238 - 8array[1][2] - 0x7ffccc482244 - 20array[2][2] - 0x7ffccc482250 - 32array[0][3] - 0x7ffccc48223c - 12array[1][3] - 0x7ffccc482248 - 24array[2][3] - 0x7ffccc482254 - 36不出所料,此时内存的偏移值不再是顺序增加。并且仔细计算可以发现,同一列的元素如 array[0][0] 和 array[1][0] 之间差了3个整形元素所占的大小 - 12字节,而这正好是一行元素的整体大小。
显然,我们可以得到一个结论:在二维数组中,数组元素是按照行的主序来存储的,也就是内存按行分配,一行分配完再分配下一行,并且是连续的。分配的过程我们可以用下图来形象的表示:
分配完成后即可得到最终的内存排列:
实际上,这种存储方式有一种专业的名词:行优先存储(Row-major order)。如果仔细观察上面的元素地址偏移,可以发现这种存储方式最显著的特点就是先把位于右侧的下标排满。
这个特点可以让我们拓展到更高维的数组存储方式。
如果我们定义一个三维数组 array[x][y][z],那么其在内存中的排列方式就是先将z维排满,再将y维排满,最后将x维排满。为了加深理解,我们再结合实际的代码:
#include
jay@jaylinuxlenovo:~/test$ ./test2 array[0][0][0] - 0x7fffef296740 - 0array[0][0][1] - 0x7fffef296744 - 4array[0][0][2] - 0x7fffef296748 - 8array[0][0][3] - 0x7fffef29674c - 12array[0][1][0] - 0x7fffef296750 - 16array[0][1][1] - 0x7fffef296754 - 20array[0][1][2] - 0x7fffef296758 - 24array[0][1][3] - 0x7fffef29675c - 28array[0][2][0] - 0x7fffef296760 - 32array[0][2][1] - 0x7fffef296764 - 36array[0][2][2] - 0x7fffef296768 - 40array[0][2][3] - 0x7fffef29676c - 44array[1][0][0] - 0x7fffef296770 - 48array[1][0][1] - 0x7fffef296774 - 52array[1][0][2] - 0x7fffef296778 - 56array[1][0][3] - 0x7fffef29677c - 60array[1][1][0] - 0x7fffef296780 - 64array[1][1][1] - 0x7fffef296784 - 68array[1][1][2] - 0x7fffef296788 - 72array[1][1][3] - 0x7fffef29678c - 76array[1][2][0] - 0x7fffef296790 - 80array[1][2][1] - 0x7fffef296794 - 84array[1][2][2] - 0x7fffef296798 - 88array[1][2][3] - 0x7fffef29679c - 92掌握了上述方法后,即使是四维,五维甚至更高维的数组,也能立刻搞明白其内存排列的方式。相信在后续处理数组的场合中,大家能够根据数组内存的排列方式写出更高效、稳健的代码。