【问题标题】:C - Local variables have the same address and valueC - 局部变量具有相同的地址和值
【发布时间】:2021-11-12 17:16:22
【问题描述】:

我有以下 C 代码:

void testA() {
    int x = 56;
    printf("Address of x = 0x%x - Value of x = %d\n",&x,x);
}

void testB() {
    int y;
    printf("Address of y = 0x%x - Value of y = %d\n",&y,y);
}

int main() {
    testA();
    testB();
    return 0;
}

打印结果如下:

Address of x = 0x61fdec - Value of x = 56
Address of y = 0x61fdec - Value of y = 56

为什么testB() 的局部变量ytestA() 的局部变量x 的地址相同,并且也继承了它的值?它们甚至不在同一个范围内,而且都不是全局变量。

【问题讨论】:

  • 不能同时存在,为什么不能共享地址?
  • 访问未初始化的变量会导致不确定的值。这些变量位于堆栈或寄存器中。当函数退出时,堆栈/寄存器可能会或可能会在下一次函数调用中再次使用之前被覆盖。如果下一个函数使用相同的堆栈/寄存器,则结果值是不确定的。可能与前一个函数设置的相同,也可能是其他随机值。
  • @kaylum 我的问题是关于地址和存储在其中的值的相似性,而不是关于未定义变量的未定义行为
  • 问题是推测未定义的行为。关于未定义行为的推测基本上没有意义。

标签: c pointers memory memory-address local-variables


【解决方案1】:

这个问题有两个完全不同的答案。

一个是C实现的工作是为对象分配地址。这不是你的工作,也可以说不是你关心的问题。在非常广泛的范围内,C 实现允许为您的对象分配任何地址,因此您不应该对它分配的任何内容感到惊讶。一个新的范围内对象恰好与刚刚超出范围的不同对象具有相同的地址?嗯,这就是“任何东西”。不是不可能,也不是奇怪,不是你的问题。

同样,未初始化变量的初始值是不确定的。你不知道它可能是什么;它可能是任何东西。它恰好与前一个函数中的前一个变量具有相同的值?再说一次,那是“任何事情”,这不是不可能的,也不是你的问题。

现在,我知道,这不是你的问题。您认为新变量恰好具有与前一个变量完全相同的地址和完全相同的值,这不仅仅是巧合。你在想象它一定意味着什么。 所以这是第二个答案。

许多 C 实现将函数的局部变量存储在 堆栈 上。每个变量的地址通常定义为函数调用的堆栈帧中的偏移量。当一个函数返回时,它的栈帧从栈中弹出,当下一个函数被调用时,它的栈帧将占据栈中相同的、新释放的部分。因此,如果上一个和下一个函数都具有相同类型的相同变量,则它们相对于堆栈帧的偏移量可能相同。因此,如果上一个堆栈帧和下一个堆栈帧位于堆栈上的同一位置,那么这些堆栈帧中的变量也将位于相同的地址。

此外,当一个函数返回时,虽然它的堆栈帧从堆栈中弹出,但 不是 意味着任何东西实际上都被清除了。当一个新调用的函数分配了它的堆栈帧时,此时也没有任何东西被清除。因此,如果一个函数有一个未显式初始化的局部变量,它的实际初始值——我们所说的“不确定”值——实际上将是堆栈上剩余的任何位模式,由堆栈帧为的最后一个函数留下那里。因此,如果最后一个函数在相同的堆栈帧偏移量处具有相同的变量,您可能会发现,你瞧,下一个函数的相同偏移量变量将开始包含与前一个函数的变量相同的值。

但这显然都是偶然和偶然,不是你可以依赖的任何东西。您听说的局部变量保留它们的值(更不用说另一个函数的值)是完全正确的。如果您不希望函数 testB 中的 y 之类的未初始化变量一开始就包含令人惊讶的值,那么请将其初始化为对 testB 有意义的适当值。

【讨论】:

    【解决方案2】:

    这是因为,在 TestA 函数调用结束时,x 超出范围并被清理。之后,y 被创建并分配了相同的内存位置。

    注意以下代码中变量的作用域相同,但地址不同:

    #include <stdio.h>
    
    void test() {
        int x = 56;
        printf("Address of x = 0x%x - Value of x = %d\n",&x,x);
        int y;
        printf("Address of y = 0x%x - Value of y = %d\n",&y,y);
    }
    
    int main() {
        test();
        return 0;
    }
    

    【讨论】:

    • 这对于地址来说是有道理的,但是如果x被清理了就意味着x的值应该被删除,那么为什么x的值也传递给y,是不是新变量恰好在同一个地址?
    • @WassimTahraoui:关于“如果 x 被清理,则意味着 x 的值应该被删除”:是什么让你认为 x 被“清理”了?你认为“清理”是什么意思。 C 标准中关于x 的所有内容都是为它保留内存,直到它声明的块执行结束。所以它说的关于x 生命周期结束的唯一一件事就是不再保留内存。这并不意味着任何东西都会改变内存或以任何方式清理它。说真的,你从哪里得到任何东西都可以清理的想法x
    • @WassimTahraoui 将内存想象成一个网格。当您声明 x 并对其进行初始化时,其中一个框将填充 x 所引用的值 56。当 x 超出范围时,x 会被清除,但值 56 会一直保留在那里,直到它被更改。现在你声明一个变量 y。 Y 引用了第一个可用位置,目前拥有 56 个。
    【解决方案3】:

    因为他们可以。

    C 标准只要求同时退出的 distinct 对象具有不同的(读取不等)地址。在您的情况下,任何变量 xy 都不能同时有效。所以实现可以为它们分配相同的地址。

    【讨论】:

      【解决方案4】:

      C 2018 6.2.4 2 说:

      对象的生命周期是程序执行过程中保证为其保留存储空间的部分……

      在没有staticextern 的函数内声明标识符的对象具有自动存储持续时间。 C 实现自动为它们保留内存并自动释放保留。

      生命周期在对象所在的块开始执行(如果它不是可变长度数组)或执行到达声明时(如果它是可变长度数组)开始。

      testA的主体开始执行时,内存为x保留。

      然后你将 56 放入x

      然后函数返回,块x 停止执行。所以内存不再为它保留。

      没有人来清理那段记忆。无论是在现实世界中还是在计算机中,您的妈妈都不会在您之后进行清理。

      可能会有人出现并使用该记忆。他们应该自己初始化它,但是,如果他们不这样做,他们可能会看到你在那个内存中放入了什么。

      testB 开始执行时,内存为y 保留。由于这些保留的组织方式,很容易将其与之前为x 保留的内存相同。那么y 中出现的值可能与您在x 中输入的值相同。

      当您打开优化时,编译器可能会重新设计程序并消除这种影响。或者它可能不会。

      【讨论】:

        猜你喜欢
        • 2015-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多