【问题标题】:Same memory space being allocated again & again相同的内存空间被一次又一次地分配
【发布时间】:2010-03-15 14:29:28
【问题描述】:

在每次循环迭代中,变量 j 被一次又一次地声明。那为什么它的地址不变

  • 不应该每次都给它一些随机地址吗?
  • 此编译器是否依赖?
#include<stdio.h>
#include<malloc.h>

int main()
{
    int i=3;
    while (i--)
    {
        int j;
        printf("%p\n", &j);
    }
    return 0;
}

测试运行:-

shadyabhi@shadyabhi-desktop:~/c$ gcc test.c
shadyabhi@shadyabhi-desktop:~/c$ ./a.out
0x7fffc0b8e138
0x7fffc0b8e138
0x7fffc0b8e138
shadyabhi@shadyabhi-desktop:~/c$

【问题讨论】:

  • 我错误地发布了错误的问题..我的错..对不起..我已经更新了问题
  • @Gardener:它会编译得很好。 void * 将自动编辑为 cast
  • 您现在已更改问题。您是否对其进行了测试以查看您的原始断言是否仍然正确?它甚至不是有效的代码,所以答案几乎肯定是否定的。
  • @nvl:从 void* 隐式转换为 int!?我希望不会。
  • 也许我们应该等到这个问题稳定后再发表评论!? ;)

标签: c linux unix gcc


【解决方案1】:

它是堆栈上的内存。它不是从堆中分配的。堆栈在该循环中不会改变。

【讨论】:

  • 但是,我每次都得到相同的地址.. 为什么?
  • 因为每次都是同一个变量。
  • @Adam:每次都不是“真的”同一个变量,它只是每次都在同一个位置(在这个实现中,以及几乎所有可能的 C 实现中)
【解决方案2】:

j 的地址从不改变的原因是因为编译器在进入函数时在堆栈上为 j 分配内存,而不是在 j 进入作用域时。

与往常一样,查看一些汇编代码可能有助于解释这个概念。取以下函数:-

int foo(void)
   {
   int i=3;
   i++;
      {
      int j=2;
      i=j;
      }
   return i;
   }

gcc 将其转换为以下 x86 汇编代码:-

foo:
    pushl   %ebp                 ; save stack base pointer
    movl    %esp, %ebp           ; set base pointer to old top of stack
    subl    $8, %esp             ; allocate memory for local variables
    movl    $3, -4(%ebp)         ; initialize i
    leal    -4(%ebp), %eax       ; move address of i into eax
    incl    (%eax)               ; increment i by 1
    movl    $2, -8(%ebp)         ; initialize j
    movl    -8(%ebp), %eax       ; move j into accumulator
    movl    %eax, -4(%ebp)       ; set i to j
    movl    -4(%ebp), %eax       ; set the value of i as the function return value
    leave                        ; restore stack pointers
    ret                          ; return to caller

让我们来看看这个汇编代码。第一行保存当前栈基指针,以便在函数退出时恢复,第二行设置当前栈顶为该函数的新栈基指针。

第三行是为所有局部变量分配堆栈内存的行。指令subl $8, %esp 从堆栈指针的当前顶部(esp 寄存器)中减去 8。堆栈在内存中增长,因此这行代码实际上将堆栈上的内存增加了 8 个字节。我们在这个函数中有两个整数,ij,每个都需要 4 个字节,因此它分配了 8 个字节。

第 4 行通过直接写入堆栈上的地址将i 初始化为 3。然后第 5 行和第 6 行加载并增加 i。第 7 行通过将值 2 写入堆栈上为 j 分配的内存来初始化 j。请注意,当j 在第 7 行进入作用域时,汇编代码没有调整堆栈来为其分配内存,这在之前已经处理过。

我确信这很明显,但是编译器在函数开始时为所有局部变量分配内存的原因是这样做更有效率。每次局部变量进入或超出范围时调整堆栈会导致对堆栈指针进行大量不必要的操作而无济于事。

我相信你可以自己弄清楚汇编代码的其余部分是做什么的,如果没有发表评论,我会引导你完成。

【讨论】:

  • 以防万一,如果有人需要知道如何获取汇编代码.. 使用 $gcc -S foo.c
  • @Andrew - 请告诉一个很好的资源来学习汇编语言。我只知道LC3。现在,我想了解 Core2Quad 的汇编代码。
  • 我刚刚通过 $gcc -S foo.c 看到了函数 foo 的汇编代码,而汇编代码太神秘了。该代码与 x86 代码非常不同。我在 Core2Quad 机器上运行 gcc x86_64 (ubuntu)..
  • @shadyabhi - Paul Carter 博士的“PC 汇编语言”一书很好地介绍了 x86 汇编代码,可从此处drpaulcarter.com/pcasm 免费下载。 Randall Hyde 在他的 Writing Great Code Vol 2 书中有一个免费的 pdf 附录,描述了核心 x86 指令集,homepage.mac.com/randyhyde/webster.cs.ucr.edu/…。您可以将它与 gcc -S(或 gdb 的反汇编命令)一起使用来探索编译器发出的汇编代码。
  • x86_64 怎么样? Bcos 我有 64 位 ubuntu .. 我如何理解输出 og "gcc -S"?
【解决方案3】:

为什么要不同?编译器需要堆栈空间来存储 int,并且每次通过循环时都有相同的空间可用。

顺便说一句,您实际上根本没有使用mallocj 保留在堆栈中。

【讨论】:

    【解决方案4】:

    j 和 i 分配在堆栈上,而不是在堆或 freestore 上(分别需要 malloc 或 new)。堆栈将下一个变量放在确定的位置(堆栈顶部),因此它始终具有相同的地址。虽然如果您在优化模式下运行,该变量可能永远不会“释放”,也就是说,堆栈大小不会在整个程序中发生变化,因为它只会浪费循环。

    【讨论】:

      【解决方案5】:

      j 分配在堆栈上,因此在该函数的一次调用期间,它始终具有相同的地址。

      如果您在该循环中调用main(),“内部”mainj 将具有不同的地址,因为它会在堆栈中更高。

      有关更多详细信息,请参阅维基百科上的 Hardware Stack

      【讨论】:

        【解决方案6】:

        其实你并没有使用malloc,有什么问题?

        该变量是函数的局部变量,它的空间在编译期间保留在堆栈中.. 那么为什么要在每次迭代时重新分配它呢?仅仅因为它是在循环中声明的?

        【讨论】:

          【解决方案7】:

          你没有malloc-ing。它是一个堆栈地址,所以它总是相同的,因为它总是一次又一次地在堆栈的同一个位置。

          【讨论】:

            【解决方案8】:

            如您所说在循环中声明,但它都超出范围并在每次迭代结束时被“销毁”(即它不在范围内且不存在当测试循环条件时)。因此,重复使用相同的堆栈位置是完全合法的(事实上,如果不是这种情况,那将是一个错误)。

            【讨论】:

              【解决方案9】:

              提示:你认为这会做什么?

              #include<stdio.h>
              #include<malloc.h>
              
              int main()
              {
                  int i=3;
                  while (i--)
                  {
                      int j = 42;
                      printf("%p\n", &j);
                  }
                  return 0;
              }
              

              【讨论】:

                【解决方案10】:

                正如其他答案所说,除了堆栈之外,您没有在这里分配任何东西。但是即使你修改如下代码,也不一定会改变分配地址。

                这取决于所使用的 libc,malloc 通常位于那里,但某些应用程序(尤其是 firefox)会覆盖它以供使用(内存碎片问题等)。

                #include<stdio.h>
                #include<malloc.h>
                
                int main()
                {
                    int i=3;
                    while (i--)
                    {
                        int *j = (int *) malloc(sizeof(int));
                        printf("%p\n", j);
                        free (j);
                    }
                    return 0;
                }
                

                如果您注释掉 free(j),您会注意到 j 的地址确实发生了变化。但是根据你的 libc,j 的地址可能总是会改变。

                【讨论】:

                  【解决方案11】:

                  现在,您会得到一系列泄露的分配,因为 j 没有为后续的空闲存储。 j 不一定会获得随机地址,但可能只是与之前 j 的分配相比的序列。

                  如果您在循环结束时释放 j,您可以获得与之前相同的行为,具体取决于 malloc 和 free 的实现。

                  编辑:您可能希望使用此代码重新检查打印的值。

                  【讨论】:

                  • 在这种情况下你不能释放 j,因为它不是动态分配的。
                  • 好吧,如果不是偶尔编辑问题以添加或删除对 malloc 的调用,那么答案就是在公园里散步。
                  猜你喜欢
                  • 1970-01-01
                  • 2015-07-04
                  • 2012-09-02
                  • 2013-05-24
                  • 2018-08-20
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-08-06
                  • 2013-11-04
                  相关资源
                  最近更新 更多