【问题标题】:Array base address is changing when declared inside loop在循环内声明时数组基地址正在更改
【发布时间】:2019-11-04 06:01:30
【问题描述】:

我在 for 循环中声明了一个数组并尝试打印它的基地址。

#include<stdio.h>

int main(){
  int n=16;
  for(int i=1;i<=n;i++){
    int a[i];
    int b[16];
    int c[n];
    printf("%p %p %p\n",(void *)a,(void *)b,(void *)c);
  }
  return 0;
}

输出如下:

0x7fffe6191740 0x7fffe6191770 0x7fffe6191700
0x7fffe6191740 0x7fffe6191770 0x7fffe6191700
0x7fffe6191740 0x7fffe6191770 0x7fffe6191700
0x7fffe6191740 0x7fffe6191770 0x7fffe6191700
0x7fffe6191730 0x7fffe6191770 0x7fffe61916f0
0x7fffe6191730 0x7fffe6191770 0x7fffe61916f0
0x7fffe6191730 0x7fffe6191770 0x7fffe61916f0
0x7fffe6191730 0x7fffe6191770 0x7fffe61916f0
0x7fffe6191720 0x7fffe6191770 0x7fffe61916e0
0x7fffe6191720 0x7fffe6191770 0x7fffe61916e0
0x7fffe6191720 0x7fffe6191770 0x7fffe61916e0
0x7fffe6191720 0x7fffe6191770 0x7fffe61916e0
0x7fffe6191710 0x7fffe6191770 0x7fffe61916d0
0x7fffe6191710 0x7fffe6191770 0x7fffe61916d0
0x7fffe6191710 0x7fffe6191770 0x7fffe61916d0
0x7fffe6191710 0x7fffe6191770 0x7fffe61916d0

为什么数组的基地址每次都变?是否为每次迭代分配内存。如果是这样,为什么地址在 4 次迭代中没有改变?

请解释abc在声明、内存分配和基地址方面的区别。

【问题讨论】:

  • 这高度依赖于您的操作系统。裸机微控制器和 Linux PC 会给出不同的结果。还要修复你的 printf。
  • 你不应该关心这个
  • @Clonk 我在 Windows、Linux 和在线平台上尝试过,都给出了相同的行为。
  • @melpomene %p 给出十六进制, %u 给出等效的整数值,只是没有变化。反正改成 %p。
  • 仍然不完全便携。为了 100% 正确,您必须将每个参数转换为 (void *)

标签: c arrays loops memory-management


【解决方案1】:

b 的大小在循环的每次迭代中都是相同的。编译器定位它一次,然后它就留在原地。

ac 在技术上都是可变长度数组。 c 的大小没有变化,但似乎分配在比a 更低的地址。

您的数组a 会增长,因此要使数组远离数组b,起始地址必须在堆栈中较低。而且,因为c 在堆栈上位于a 之下,所以它也会随着a 的增长而移动。编译器似乎以 16 字节的数量分配堆栈。当数组遇到其他变量时,它的起点会在堆栈中向下移动 16 个字节。

聪明的编译器可以在循环期间发现c 是一个固定大小,并对其重新排序,使其出现在堆栈中a 的上方。然后只有a 会更改地址。您的编译器似乎没有这样做。

堆栈布局似乎是:

第一个 4 个周期:

b 0x…901d0 
a 0x…901a0   gap to b is 0x30
c 0x…90160   gap to a is 0x40

第 2 4 个周期:

b 0x…901d0
a 0x…90190  gap to b is 0x40
c 0x…90150  gap to a is 0x40

第 3 个 4 周期:

b 0x…901d0
a 0x…90180   gap to b is 0x50
c 0x…90140   gap to a is 0x40

此行为完全由编译器决定。当a 有1..4 个条目时,为什么ab 之间的差距如此之大,我没有很好的解释。它可以每次将变量放置在不同的位置。从技术上讲,当循环结束时,数组会超出范围;变量在每个循环周期中重新定义。它们都没有被初始化。其中两个无法初始化;您不能为 VLA 提供初始化程序。

【讨论】:

    【解决方案2】:

    这些数组具有自动存储持续时间,并且从概念上讲,每次执行 for 循环内的 { … } 语句时都会创建每个数组的新实例。由于在各种迭代中,您要求数组a 具有不同的大小,因此C 实现将其放在内存中的不同位置以为其元素留出空间是完全合理的。您的 C 实现似乎使用 16 字节的块作为它为数组保留多少内存或对齐它的方式的单位。这可能是堆栈管理的结果,因为数组a 本身可能不需要对齐或块大小。

    abc 的分配很可能受到以下事实的影响:在 C 标准指定的抽象计算机中,b 的生命周期在执行块开始,但ac 的生命周期在执行(“控制”)到达定义它们的语句时开始。这是因为 C 2018 6.2.4 表示具有自动存储持续时间且不具有可变长度的对象在进入关联块时开始生命(第 6 段),而具有可变长度的此类对象在声明时开始生命(第 7 段)。因此,在编写代码时,b 首先开始生命,然后是 a,然后是 c

    这种分配顺序会影响c 的放置位置,但不会影响b 的放置位置。由于首先创建了b,因此它在堆栈上“更早”(在更高的地址,这意味着它获得了一个尚未受a 影响的地址)。由于c 是稍后创建的,因此它在堆栈上“更晚”(在较低的地址,这意味着它获得的地址受a 的大小影响)。 C 标准在技术上不要求此顺序,因为只要获得与 C 标准定义的相同结果,C 实现就可以随意安排位置。但是,您的实现似乎忠实地遵循了 C 的抽象计算机模型,首先创建 b,然后是 a,然后是 c

    另外,打印对象地址的正确方法是使用%p格式规范并将地址转换为void *

    printf("%p %p %p\n", (void *) a, (void *) b, (void *) c);
    

    【讨论】:

      【解决方案3】:

      自动变量分配在堆栈内存中,并且只存在于正在使用的块上。正如@Clonk 所说,它依赖于实现,因此在不同的实现中会产生不同的结果,但它们是有效的,因为数组的大小事先不知道,它们最终会出现在有连续内存块的地方。

      https://en.wikipedia.org/wiki/Variable-length_array

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-14
        • 2012-02-02
        • 1970-01-01
        • 1970-01-01
        • 2018-04-21
        • 2014-03-28
        相关资源
        最近更新 更多