C中的2D阵列如何变成1D阵列
How do 2D arrays in C become 1D arrays?
如果有人能向我解释以下行为,我将不胜感激:
假设我声明一个静态2D阵列
float buffer[NX][NY];
现在,如果我想填充这个数组,我注意到可以这样做:
initarray(buffer, NX, NY);
#define INITDATAVAL 0.5
void initarray(void *ptr, int nx, int ny)
{
int i, j;
float *data = (float *) ptr;
for (i=0; i < nx*ny; i++)
{
data[i] = INITDATAVAL;
}
}
我的问题是,如果缓冲区是一个2D数组,那么一旦它被传递给initarray
函数,它怎么能被用作1D数组呢?我很难理解…
当静态分配2D阵列时,分配的内存是连续的,但如果动态分配buffer
,是否可以使用这种方式?
内存中包含3 x 4个元素(即矩阵)的2D数组如下所示:
A1 A2 A3 A4 B1 B2 B3 B4 C1 C2 C3 C4
由于底层存储是连续的,因此可以简单地将数组转换为指向第一个元素的指针,并使用单个偏移量访问所有元素(这种"强制转换"在这种上下文中被称为"衰减",当buffer
传递给initarray
时会自动发生)。
(在本示例中,编译器将把buffer[n][m]
之类的表达式转换为buffer + n*NY+m
。基本上,2D数组只是存储在1D数组中的2D数据的一种方便的表示法)。
首先,initarray
应该采用float*
参数,而不是void*
。
将数组转换为指针时,将丢失有关维度的类型信息。您实际上是在将它转换为指向第一个元素的指针,并确认存储是连续的。
char foo [2][2] = { {'a','b'}, {'c','d'} }; // Stored as 'a', 'b', 'c', 'd'
可以使用模板保留标注信息。
template <int W, int H>
void initarray (float (&input)[W][H]) {
for (int x = 0; x < W; ++x) {
for (int y = 0; y < H; ++y) {
input [x][y] = INITDATAVAL;
}
}
}
int main () {
float array [3][4];
initarray (array);
}
这里,input
是对给定类型的数组的引用(维度是完整类型的一部分)。模板参数推导将用W=3
、H=4
实例化initarray
的重载。很抱歉使用了行话,但这就是它的工作原理。
顺便说一句,您将无法使用指针参数调用此版本的initarray
,但如果需要,您可以提供重载。我经常写这样的
extern "C" void process (const char * begin, const char * end);
template <typename N>
void process (const char * (&string_list) [N]) {
process (string_list, string_list + N);
}
这个想法是提供最通用的接口,在单独的翻译单元或库中实现一次,然后提供更友好、更安全的接口。
const char * strings [] = {"foo", "bar"};
int main () {
process (strings);
}
现在,如果我更改strings
,就不必在其他地方更改代码。我也不必考虑令人恼火的细节,比如我是否正确维护了NUMBER_OF_STRINGS=2
。
数组是一系列连续的对象。
数组也是一系列连续的对象,但这些对象恰好是数组,它们本身只是由端到端放置在内存中的元素组成的。图片:
float a[2][3];
a[0] a[1]
+-------+-------+-------++-------+-------+-------+
|float |float |float ||float |float |float |
|a[0][0]|a[0][1]|a[0][2]||a[1][0]|a[1][1]|a[1][2]|
| | | || | | |
+-------+-------+-------++-------+-------+-------+
由于这是一行中包含浮点数的一系列单元格,因此也可以将其视为6个浮点数的单个数组(如果通过适当的指针查看)。新图片:
float* b(&a[0][0]);//The &a[0][0] here is not actually necessary
//(it could just be *a), but I think
//it makes it clearer.
+-------+-------+-------++-------+-------+-------+
|float |float |float ||float |float |float |
|*(b+0) |*(b+1) |*(b+2) ||*(b+3) |*(b+4) |*(b+5) |
| | | || | | |
+-------+-------+-------++-------+-------+-------+
^ ^ ^ ^ ^ ^
| | | | | |
b b+1 b+2 b+3 b+4 b+5
如您所见,a[0][0]
变成b[0]
,a[1][0]
变成b[3]
。整个数组可以看作只是一系列的浮点,而不是一系列的浮动数组。
2D阵列的所有内存都已连续分配。
这意味着,给定一个指向数组开头的指针,数组看起来是一个大的1D数组,因为2D数组中的每一行都在最后一行之后。
数据只是按顺序存储在磁盘上。像这样:
0: buffer[0][0],
1: buffer[0][1],
. ...
NY-2: buffer[0][NY-2],
NY-1: buffer[0][NY-1],
NY: buffer[1][0],
NY+1: buffer[1][1],
. ...
NY*2-2: buffer[1][NY-2],
NY*2-1: buffer[1][NY-1],
. ...
NY*(NX-1): buffer[NX-1][0],
NY*(NX-1)+1: buffer[NX-1][1],
. ...
NY*(NX-1)+NY-2: buffer[NX-1][NY-2],
NY*(NX-1)+NY-1: buffer[NX-1][NY-1],
数组本质上是指向第一个元素的指针。因此,在for循环中所做的是顺序填充数据,而数据也可以被解释为包含整个数据块的单个数组(float[]
)或指针(float*
)。
值得注意的是,在一些(旧的/特殊的)系统上,数据可能会被填充。但所有x86系统都填充到32位边界(这是浮点大小),编译器通常(至少是MSVC)打包到32位对齐,所以这样做通常是可以的。
编辑后的问题的部分答案:
当静态分配2D阵列时,分配的内存是连续的,但如果动态分配缓冲区,可以使用这种方式吗?
可以将静态分配的2D数组视为1D数组的原因是,编译器知道维度的大小,因此可以分配连续块,然后当您在缓冲区[x][y]中使用索引运算符时,它会计算该内存的索引。
当您动态分配内存时,您可以选择将其设为1D或2D,但不能像对待静态分配的数组那样将其同时处理,因为编译器不会知道最内部维度的大小。所以你可以:
- 分配一个指针数组,然后为每个指针分配一个1D数组。然后可以使用buffer[x][y]语法
- 分配一个1D数组,但您必须在from缓冲区[y*x_dim+x]中手动计算索引
一个2D数组在内存中连续排列,因此使用正确的类型punning,您可以将其视为已声明为1D数组的:
T a[N][M];
T *p = (&a[0][0]);
所以
a[i][j] == p[i*N + j]
除非是sizeof
或一元&
运算符的操作数,或者是用于初始化声明中的数组的字符串文字,否则类型为"T
的N元素数组"的表达式将转换为类型为"指向T
的指针"的表达式,其值为数组的第一个元素的地址。
当你呼叫时
initarray(buffer, NX, NY);
将表达式buffer
替换为类型为"指向NY
的指针-float
的元素数组"或float (*)[NY]
的表达式,并将此表达式传递给initarray
。
现在,表达式buffer
和&buffer[0][0]
的值是相同的(数组的地址与数组中第一个元素的地址相同),但类型不同(float (*)[NY]
与float *
相反)。这在某些情况下很重要。
在C中,可以将void *
值分配给其他对象指针类型,反之亦然,而无需强制转换;这在C++中不是真的。我很好奇g++是否对此提出了任何警告。
如果是我,我会明确地传递缓冲区的第一个元素的地址:
initarray(&buffer[0][0], NX, NY);
并将第一个参数的类型从void *
更改为float *
,以使一切尽可能直接:
void initarray(float *data, int nx, int ny)
{
...
data[i] = ...;
...
}
- OpenMP阵列性能较差
- 函数何时会在c++中包含stack_Unwind_Resume调用
- 如何将三维尺寸不固定的三维阵列展平为一维阵列
- 当我的阵列太大时出现分段错误
- Python中的for循环与C++有何不同
- 位阵列上的快速AND运算
- 阵列必须使用大括号封闭的初始器进行初始化
- 没有从阵列<float>到阵列<int>的可行转换
- 为什么 -1 在 C++ 中变成 -842150451?
- C++动态安全 2D 交错阵列
- 将平面阵列重塑为复杂的特征类型
- 如何使用英特尔 PIN 捕获阵列的所有负载?
- 在C++中释放内存期间,迭代器与指针有何不同
- 为什么我能够为阵列分配比计算机实际拥有的内存更多的内存
- 库特<<恩德尔;不适用于打印 2D 阵列
- 数组类 阵列的打印输出
- 从较小的阵列到较大的阵列的元素级转换
- 仅在大型阵列上出现合并排序分段错误
- 初始化的std ::阵列从指针优雅地变成缓冲区
- C中的2D阵列如何变成1D阵列