同一内存空间被反复分配

6
在每个循环迭代中,变量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$

你现在改变了问题。你测试过它以查看原始断言是否仍然正确吗?它甚至不是有效的代码,所以几乎可以肯定答案是否定的。 - Clifford
@nvl:从void*到int的隐式转换?希望不是这样。 - Clifford
2
也许在进一步评论之前,我们应该等待这个问题稳定下来!;) - Clifford
@Clifford:是的!malloc在隐式转换方面没有问题。你可以自己检查一下。我总是隐式地使用它。 @shadyabhi:如果你使用malloc,那么每次j都会被分配不同的内存,只有当你每次不释放它时才会这样。 - N 1.1
@Clifford:我只在使用malloc时使用隐式转换,因为GCC会进行隐式转换,并且不会抛出任何警告,因为这样做是完全安全的。在C中,不需要强制转换由malloc返回的指针。请查看以下链接以获取更多信息:http://stackoverflow.com/questions/1942159/need-some-clarification-regarding-casting-in-c/1942276#1942276 https://dev59.com/XUrSa4cB1Zd3GeqPX55J#1835201 https://dev59.com/dHRB5IYBdhLWcg3wgHWr#605858 现在我表达清楚了吗? :) - N 1.1
显示剩余6条评论
11个回答

12

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


但是,我每次都得到相同的地址..为什么? - shadyabhi
1
因为每次都是相同的变量。 - Adam Goode
5
@Adam: 每次使用的变量并不是“完全相同”的,它只是每次都在相同的位置(在这个实现中以及几乎任何可行的 C 语言实现中)。 - Steve Jessop

6

变量 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字节的堆栈内存。在此函数中有两个整数i和j,每个都需要4个字节,因此它分配了8个字节。
第4行通过直接写入堆栈上的地址将i初始化为3。然后第5行和第6行加载并递增i。第7行通过将值2写入为j分配的内存来初始化j。请注意,当j在第7行进入作用域时,汇编代码没有调整堆栈以为其分配内存,这已经在前面处理过了。
我相信这很明显,但编译器在函数开始时分配所有本地变量的内存的原因是因为这样做更有效率。每次本地变量进出作用域时调整堆栈会导致大量不必要的堆栈指针操作而没有任何收益。
如果你还不确定这段汇编代码的其余部分是做什么的,那没关系,请留言,我会帮你解释。

以防万一,如果有人需要知道如何获取汇编代码.. 使用 $gcc -S foo.c - shadyabhi
@Andrew - 请告诉我一个好的学习汇编语言的来源。我只了解LC3。现在,我想了解Core2Quad的汇编代码。 - shadyabhi
我刚刚通过执行 $gcc -S foo.c 命令看到了函数foo的汇编代码,但是这段代码过于晦涩难懂。它与x86代码非常不同。我在一台Core2Quad机器上运行gcc x86_64(ubuntu)。 - shadyabhi
@shadyabhi - 保罗·卡特博士的《PC汇编语言》是学习x86汇编代码的绝佳入门书籍,可以从这里免费下载http://www.drpaulcarter.com/pcasm/。Randall Hyde在他的《编写优秀代码第2卷》一书中提供了免费的PDF附录,描述了核心的x86指令集,http://homepage.mac.com/randyhyde/webster.cs.ucr.edu/www.writegreatcode.com/Vol2/wgc2_OA.pdf。您可以使用此附录以及gcc -S(或gdb的反汇编命令)来探索编译器生成的汇编代码。 - Andrew O'Reilly
x86_64怎么样?因为我有64位的Ubuntu.. 如何理解"gcc -S"的输出呢? - shadyabhi
我还没有时间学习x86_x64,所以恐怕我没有任何推荐。据我所知,x64与x86非常相似,因此学习x86并不是浪费时间。您可以通过-m32选项在x64系统上将gcc设置为x86模式。当我有时间学习x64时,我可能会从某个地方获取指令集摘要,并开始逐步分析gcc为各种用例创建的汇编代码。 - Andrew O'Reilly

6

为什么要不同?编译器需要在堆栈上分配空间来存储一个int,在每次循环时都有相同的空间可用。

顺便说一下,你实际上根本没有使用mallocj 保存在堆栈上。


4

j和i是分配在栈上的,而不是堆或自由存储区(这需要malloc或new)。栈将下一个变量放在确定位置(栈顶),因此它始终具有相同的地址。但是,如果您正在运行优化模式,则该变量可能永远不会“dealloced”,即栈大小在整个程序中不会发生变化,因为这只会浪费周期。


2

j是在堆栈上分配的,所以在函数的每次调用中,它始终具有相同的地址。

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

有关详细信息,请参见维基百科上的硬件堆栈


2

实际上,你并没有使用malloc,那么问题出在哪里呢?

这个变量是函数内的局部变量,在编译期间它的空间被保留在堆栈中...所以为什么每次迭代都要重新分配它呢?只是因为它在循环内声明了吗?


1
你没有使用 malloc。这是一个栈地址,因此它始终相同,因为它总是在堆栈的同一位置。

1

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


0

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

#include<stdio.h>
#include<malloc.h>

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

0

正如其他答案所说,您在这里没有分配任何东西,而是在堆栈上。但是,即使您将代码修改为以下内容,也不一定会更改分配地址。

这取决于使用的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的地址可能总是会变化。


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