【问题标题】:How Are C Arrays Represented In Memory?C 数组如何在内存中表示?
【发布时间】:2011-12-12 05:50:58
【问题描述】:

如果您使用 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                | 
+---------------------+-------------+----------+----------------------+

这接近发生的情况,还是完全不成立?

【问题讨论】:

  • 出于兴趣,在第一个示例中,您为什么将xPtr 地址分开这么远?没有什么可以禁止实现这样做,但通常给定函数的自动变量将全部位于“堆栈”上。
  • 这只是为了避免混淆并突出重点。我知道他们通常更接近。
  • 我想知道 std::vector 是否相同。我听说在某个时候有一些更新,以保证向量在内存中是连续的,但我仍然想知道内存表示是什么......

标签: c arrays memory-management


【解决方案1】:

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

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

也就是说,x 是五个int,并且只有一个地址。

数组的怪异之处不在于它们的存储方式,而在于它们在表达式中的求值方式。如果您在某处使用的数组名称不是一元 &sizeof 运算符的主题,则它的计算结果是其第一个成员的地址。

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

另一方面,如果你写&x,那么特殊规则不适用——所以& 操作符像往常一样工作,这意味着它获取数组的地址。在示例中,这将是一个值 10568,类型为 int (*)[5]

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 我赞成这个解释,但我不确定标题中的实际问题是否得到了回答:p
    【解决方案3】:

    是的,你明白了。 C 数组通过计算 x + (y * sizeof(type)) 找到索引值 x[y]x 是数组的起始地址。 y * sizeof(type) 是一个偏移量。 x[0] 产生与 x 相同的地址。

    多维数组也是类似的,所以int x[y][z]会消耗sizeof(int) * y * z的内存。

    因此,您可以做一些愚蠢的 C 指针技巧。这也意味着获取数组的大小(几乎)是不可能的。

    【讨论】:

      【解决方案4】:

      C 中的数组是一个连续的内存块,每个成员的内存块大小相同。这就是指针起作用的原因,您根据第一个成员的地址寻找偏移量。

      【讨论】:

        【解决方案5】:

        Arrays and Pointers section in the C FAQ 有一些有用的信息。

        【讨论】:

          【解决方案6】:

          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
          

          【讨论】:

          • 给予或接受,静态分配的数组aren't identical to pointers,只是在使用上相当。 (既然 OP 试图弄清楚 C 的细节,那还不如对一切都迂腐。)
          • 不。如果我们有int *foofoo + 5 将指向foo 之后的第六个整数。对于 8 位字节和 32 位整数,5 * sizeof(*foo) 将在foo 上加 20,导致访问foo 之后的第 21 个整数,这实际上可能超出范围。为了使您的陈述正确,您需要*((int *)(((char *) foo) + 5*(sizeof(*foo))),给或带几个括号;-)
          【解决方案7】:

          丹尼尔,

          这并不难。您有基本的想法,并且数组的内存表示没有太大区别。如果你声明一个数组,说

               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]);
              } 
          

          您将看到每个地址以 4 的间隔递增。(如果您的整数是 32 位长)。 所以你可以很容易地理解数组是如何存储在内存中的。

          你也可以用不同的方法尝试同样的事情。

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

          你会在这两种情况下得到相同的答案,并尝试得到等价的答案。

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

          【讨论】:

            【解决方案8】:

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

            只是一个指针

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

            【讨论】:

            • int x[] 根本不是指针。它是一个数组,只有数组可以被视为指向第一个数组元素的指针这一事实才会产生与最初已经是指针一样的结果。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-07-19
            • 2013-12-17
            • 2012-06-28
            • 1970-01-01
            • 2011-02-16
            • 1970-01-01
            • 2015-02-12
            相关资源
            最近更新 更多