C数组在内存中是如何表示的?

28

我相信我理解了如果你在使用C语言时,普通变量和指针在内存中的表示方式。

例如,很容易理解一个名为Ptr的指针将具有地址,并且它的值将是不同的地址,即它所指向的内存空间。以下代码:

int main(){
    int x = 10;
    int *Ptr;
    Ptr = &x;
return 0;
}

在内存中会有以下表示:

+---------------------+-------------+---------+
| Variable Name       | Address     | Value   | 
+---------------------+-------------+---------+
| x                   | 3342        | 10      |
+---------------------+-------------+---------+
| Ptr                 | 5466        | 3342    |
+---------------------+-------------+---------+

然而,我发现很难理解数组在内存中的表示方式。例如,下面这段代码:

int main(){
    int x[5];
        x[0]=12;
        x[1]=13;
        x[2]=14;

    printf("%p\n",(void*)x);
    printf("%p\n",(void*)&x);

return 0;
}

这段代码输出了相同的地址两次(为了简单起见,是10568)。这意味着x==&x。但是*x(或使用数组符号表示的x[0])等于12,*(x+1)(或使用数组符号表示的x[1])等于13,以此类推。如何表示这个结果?一种方式可能是:

+---------------------+-------------+----------+----------------------+
| Variable Name       | Address     | Value    | Value IF array       |
+---------------------+-------------+----------+----------------------+
| x                   | 10568       | 10568    | 12                   |
+---------------------+-------------+----------+----------------------+
|                     | 10572       |          | 13                   | 
+---------------------+-------------+----------+----------------------+
|                     | 10576       |          | 14                   | 
+---------------------+-------------+----------+----------------------+
|                     | 10580       |          | trash                | 
+---------------------+-------------+----------+----------------------+
|                     | 10584       |          | trash                | 
+---------------------+-------------+----------+----------------------+

这是否接近实际情况,或者完全错了?


3
相关的内容。 - emboss
出于好奇,在第一个例子中,为什么您将xPtr的地址分得这么远?没有什么禁止实现这样做,但通常给定函数的自动变量都会靠近彼此,在“堆栈”上。 - Steve Jessop
这只是为了避免混淆并突出重点。我知道它们通常要接近得多。 - Daniel Scocco
我想知道 std::vector 是否也是这样。我听说过有一次更新,以确保向量在内存中是连续的,但我仍然想知道内存表示是什么... - jokoon
8个回答

36

数组是一块连续的对象,没有空隙。这意味着在你的第二个示例中,x 在内存中被表示为:

+---------------------+-------------+---------+
| Variable Name       | Address     | Value   | 
+---------------------+-------------+---------+
| x                   | 10568       | 12      |
|                     |             +---------+
|                     |             | 13      |
|                     |             +---------+
|                     |             | 14      |
|                     |             +---------+
|                     |             | ??      |
|                     |             +---------+
|                     |             | ??      |
+---------------------+-------------+---------+

也就是说,x 是五个 int 大小,并且有一个单独的地址。

关于数组的奇怪部分不在于它们如何存储,而在于它们在表达式中的求值方式。如果你在某个地方使用数组名,并且它不是一元操作符 &sizeof 的主题,那么它将求值为其第一个成员的地址。

也就是说,如果你只写了 x,你会得到一个类型为 int * 的值 10568。

另一方面,如果你写了 &x,那么特殊规则就不适用 - 因此,& 操作符的作用就像通常一样,它获取数组的地址。在本例中,这将是一个类型为 int (*)[5] 的值 10568。

x == &x 的原因是,数组的第一个成员的地址必须等于数组本身的地址,因为数组从其第一个成员开始。


22

您的图表是正确的。关于&x周围的奇怪现象与数组在内存中的表示方式无关,而与数组指针衰减有关。在值上下文中,单独的x会衰减为指向其第一个元素的指针;即,它等同于&x[0]&x是一个指向数组的指针,而两者数值相等只是说一个数组的地址在数值上等于其第一个元素的地址。


我点赞了这个解释,虽然我不确定标题中的实际问题是否得到了回答 :p - Jason K.

2
是的,你说得对。C数组通过计算来找到索引值x[y]x是数组的起始地址。y*sizeof(type)是从该地址的偏移量。x[0]产生与x相同的地址。
多维数组也是类似的,所以int x[y][z]将消耗sizeof(int)*y*z内存。
由于这个原因,你可以做一些愚蠢的C指针技巧。这也意味着获取数组大小(几乎)是不可能的。

0

C数组就是一块具有相同大小的连续值的内存块。当你调用malloc()时,它只是授予你一块内存块。foo[5]*(foo + 5)是相同的。

示例 - foo.c:

#include <stdio.h>

int main(void)
{
    int foo[5];
    printf("&foo[0]: %tx\n", &foo[0]);
    printf("foo: %tx\n\n", foo);
    printf("&foo[3]: %tx\n", &foo[3]);
    printf("foo: %tx\n", foo + 3);
}

输出:

$ ./foo
&foo[0]: 5fbff5a4
foo: 5fbff5a4

&foo[3]: 5fbff5b0
foo: 5fbff5b0

大致而言,静态分配的数组与指针并不相同,只是在使用上等效。(由于OP试图弄清楚C的细节,因此最好对所有内容都进行一些学究式的解释。) - millimoose
不对。如果我们有 int *foo,那么 foo + 5 将指向 foo 后的第六个整数。对于 8 位字节和 32 位整数,5 * sizeof(*foo) 将会在 foo 上加上 20,导致访问 foo 后的第 21 个整数,这可能超出了边界。为了使你的语句正确,你需要使用 *((int *)(((char *) foo) + 5*(sizeof(*foo))),括号可能略有不同 ;-) - Sinan Ünür

0

丹尼尔,

这不难。你已经有了基本的想法,在数组的内存表示方面没有太大的区别。如果你声明一个数组,比如说

     void main(){
         int arr[5]={0,1,2,3,4};


     }

你已经初始化(定义)了数组。因此,这五个元素将存储在内存中的五个相邻位置。你可以通过引用每个元素的内存地址来观察这一点。 与 C 语言中的其他原始数据类型不同,数组标识符(这里是arr)本身就代表着它的指针。如果你是初学者,这个想法可能有些模糊,但随着你的学习深入,你会感到舒适自如。

      printf("%d",arr);

这行代码将显示第一个元素arr[0]的内存地址。这类似于引用第一个元素的地址。

      printf("%d",&arr[0]);

现在,您可以查看所有元素的内存位置。以下代码片段将完成此任务。
    int i;
    for(i=0;i<5;i++){
       printf("location of %d is %d\n",arr[i],&arr[i]);
    } 

如果你的整数是32位长,你会看到每个地址都会以四个为间隔增加。

因此,你可以轻松地理解数组在内存中的存储方式。

你也可以尝试使用其他方法来做同样的事情。

    int i;
    for(i=0;i<5;i++){
       printf("location of %d is %d\n",*(a+i),a+i);
    }

在两种情况下,您将获得相同的答案集,并尝试获得等效性。

使用不同的数据类型(char、float和struct类型)尝试相同的实验。您将看到相邻元素之间的间隙如何根据单个元素的大小而变化。


0
在C语言中,数组是一段连续的内存块,每个成员的内存块大小相同。这就是为什么指针可以工作,因为你可以基于第一个成员的地址来寻找偏移量。

0

-1

int x[] 与 int* x; 产生相同的结果;

它只是一个指针

因此,符号 x[i] 和 *(x + i) 产生相同的结果。


int x[] 其实根本不是一个指针。它是一个数组,只有因为数组可以被当作指向第一个数组元素的指针而产生了和一开始就是指针时相同的结果。 - Christian
数组不是指针。 - R. Martinho Fernandes

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