C/C++中的数组是如何在内部工作的?

23

我想知道在C语言中数组是如何工作的,最后得出了一个假设,并想知道我是否正确。

我们知道数组是一系列相邻的内存盒子(boxes),每个盒子的大小都等于它所储存的类型的大小(例如,如果是INT,则一个盒子的大小为sizeof(int),而3个INT的数组在内存中占据3个相邻的sizeof(int)的位置)

现在我们还知道,我们可以动态分配一个特定类型的数组的内存(在C中使用malloc,C++中使用new)。

让我想到疑惑的是,数组的起源是数组的第一个盒子的地址,当用括号[0]调用它时,第一个值(后面的盒子中的值)是array [0] == *(array + 0)== * array(无论数组是声明为"type * array"还是"type array []"或"type array [size]"),并且这样调用的"array"无论是定义为指针还是数组("type * array"或"type array []"或"type array [size]"),都是第一个盒子的地址。

我最终认为并希望确认:即使用方括号[]声明了数组,在内存中它实际上是n个指针序列,每个指针包含(值而不是地址)实际值的内存盒子Bi的地址+那些内存盒子(B0,...,Bn每个都包含实际值)。因此,当一个人声明"int array [5]"时,程序实际上分配了5个相邻的int指针P0,P1,..,P4和分散在计算机内存中的5个int大小的内存位置B0,B1,...,B4,其中Pi的值是Bi的地址。

enter image description here

我是正确还是错误!!?? 谢谢!


6
不,除非你有一个 int *arr[],否则数组中包含实际的整数。 - chris
1
阅读 comp.lang.c FAQ 的第 6 部分。 - Keith Thompson
4个回答

13

数组在内存中实际上是n个指针的序列,每个指针包含一个指向实际值所在的内存盒子Bi的地址。

不是这样的。

你可能会想知道为什么array[0] == *(array+0) == *array既适用于声明为int array[10]; 的数组,也适用于int *array = ...;。这是一个完全合理的问题;我们被告知对于指针ptr,表达式*ptr会得到指针指向的值,那么当我们使用相同的语法来处理数组时,我们正在解引用哪些地址呢?

这是秘密:数组下标运算符([])在C和C++中并不适用于数组。当您将其应用于数组时,语言会隐式地将数组转换为指向数组第一个元素的指针。因此,对数组进行加法操作或解引用数组似乎与对指针进行加法操作或解引用相同。

int array[10];

// These lines do exactly the same thing:
int *ptr1 = &array[0]; // explicitly get address of first element
int *ptr2 = array;     // implicitly get address of first element

数组实际上是内存中一系列连续的元素,每个元素都是值本身而不是指向包含该值的另一个位置的指针。只是因为数组的定义方式通常会隐式地转换为指针,所以看起来像是有指针,但实际上只有隐式的转换。


数组索引运算符([])适用于数组。当然,它是为此而设计的。但是该运算符所执行的操作与指针加法相同。 - user207421
8
不是的,如果你阅读C++规范,你会发现下标运算符只定义为与类型为“指向T的指针”的表达式配合使用,而不是与数组类型的表达式配合使用。在数组上使用下标运算符之所以与在指针上使用它相同,是因为在数组上使用它首先将数组转换为指针,然后实际上它就是相同的了。 - bames53
1
@EJP 另一种看待这个问题的方式是,如果你在类似于 int main() { int array[10]; array[0]; } 的代码上使用 clang 的 AST dump 功能,AST 将会显示一个名为 ArraySubscriptExpr 的节点,该节点左侧有一个名为 ImplicitCastExpr <ArrayToPointerDecay> 的节点。 - bames53

4

数组在虚拟内存中是连续存储的。然而,它们映射到的物理内存地址可能是连续的也可能不是。

而且数组元素并不存储指向下一个元素的指针,只有值被存储。


2

可以这样理解:

array[n] 只是 *(array + n) 的一种简写方式。

而且,这里没有指针,数组实际上包含在一个连续的内存范围内。


是的,这就是我写的,并且我也理解了。但我的问题是,当一个人执行 "int array [n]"(或任何其他类型)时,在内存分配方面会发生什么? - Paiku Han
1
* 表示解引用,因此您可以从位置 n 获取 。如果您所写的内容是正确的,那么就需要进行额外的解引用步骤(但是从符号表示中可以清楚地看出这种情况并不存在)。 - Karoly Horvath
1
你看到图片中绿色部分了吗?直接在那里写入值就是发生的事情。实际分配取决于它是本地变量还是动态分配的变量。每个变量都有自己(特定于实现的)规则,但这与数组无关。 - Karoly Horvath

0

数组不包含任何指针。数组的元素存储在堆上,而对这些元素的引用存储在栈上。如果声明一个名为values的类型为int的数组,它由5个元素组成。

变量values是指向存储在堆上的第一个值values[0]的指针,也称为基地址,即数组的第一个元素的地址。你可能会想知道代码如何找到数组的其他元素。values[1]可以通过*(values+1)进行解引用,或者在低级别上,它是这样的*(&values + n*sizeof(values[0])),其中n是您要解引用的元素的索引。

因此,只需将元素的大小添加到内存中,就可以获取数组的其他元素。这是因为数组的元素在内存中是“并排”的,或者在技术上说,内存块共享相同的边界。

数组没有任何指针,像包含指针的数据结构的数组称为链表。

你可以在这里了解数组的内部工作原理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接