【问题标题】:Is it legal to reference a variable in its definition?在定义中引用变量是否合法?
【发布时间】:2012-10-29 07:31:01
【问题描述】:
int test[2] = { 45, test[0] };
int x = (x = 111);
cout << test[0] << " " << test[1] << " " << x << "\n"; // 45 45 111

前两行的赋值合法吗? Visual Studio 2010 编译并运行它时没有任何错误或警告,但这似乎是一个可能未定义的奇怪情况,所以我想确认它是可以接受的。如果我做了像int x = x; 这样公然自反(并且可能是未定义)的事情,Visual Studio 会警告我,所以我想知道它似乎允许的这些情况是如何处理的。

【问题讨论】:

  • int x = (x + 1) 也编译并将x 设置为1 毫无价值(无论如何,在Linux 上使用GCC)。
  • @apsillers:你依赖于未定义的行为。 x 在评估 (x + 1) 时未初始化。
  • 这也可以在 linux 上编译。结果:45 45 111 确实这是一个奇怪的情况,但似乎是正确的!我猜编译器可以解决问题。当这些行转换为程序集时,它们是按顺序排列的,因此当使用的值已经存在。
  • 那 (int x = (x + 1);) 是明确未定义的行为;您正在增加一个不确定的值,该值不能保证为零。
  • @apsillers:你很幸运。在我的 GCC 上,默认值为 potato

标签: c++ variables initialization standards variable-assignment


【解决方案1】:

来自 C++ 标准(C++11,但在 C++98/03 中没有什么不同):

(§ 3.3.2/1) 名称的声明点紧接在其完整声明符之后(第 8 条)和其初始化器(如果有)之前, [...] [ 示例:

int x = 12;
{ int x = x; }

这里第二个 x 用它自己的(不确定的)值初始化。 —结束示例]

这适用于用户定义的类型以及数组类型。请注意标准如何强调第二个示例中的 x 是用 indeterminate 值初始化的。所以没有办法知道x的初始化值是什么。

【讨论】:

    【解决方案2】:

    我假设你在某个函数中,因为你正在调用函数等。

    testx 的空间都分配在 堆栈 上。理论上,这些家伙的空间在他们的值被填充之前应该存在。如果我们查看生成的程序集(x86 gcc),这是真的。

    subl    $40, %esp         # Add 40 bytes of memory to the current stack
    movl    $0, -20(%ebp)     # Clear test[0] to 0
    movl    $0, -16(%ebp)     # Clear test[1] to 0
    movl    $45, -20(%ebp)    # Place the value of 45 into test[0]
    movl    -20(%ebp), %eax  # Copy that 45 into a register
    movl    %eax, -16(%ebp)  # Move that register's value (45) into test[1]
    movl    $111, -12(%ebp)  # Assign x to be 111, optimize out the unnecessary duplicate assignment
        ... #continues on to set up and call printf
    

    我们可以看到堆栈中添加了 40 个字节。请注意 test[0]、test[1] 和 x 的地址是如何从%ebp 以 4 字节间隔(分别为 -20、-16、-12)标记的连续地址。 它们在内存中的位置是存在的,并且在定义它们之前可以无错误地访问。这里的编译器将它们都清除为 0,尽管我们可以看到这是不必要的。您可以删除这两行并且仍然可以正常运行。

    我们可以从中得出的结论是您的 int test[2]int x 可以在其中包含任意数量的时髦循环引用他们自己和代码将编译 - 这只是您的工作,以确保您的引用获取良好的数据(即以某种方式初始化的数据)而不是垃圾,您已经在这里完成了。这也适用于其他情况 - 编译为程序集并自己检查它是如何完成的。

    【讨论】:

      【解决方案3】:

      这是合法的,因为变量的声明在你初始化它的时候就已经完成了。但是,test[1] 的值是未定义的。

      【讨论】:

      • 怎么样?它是 45。它获取test[0] 的值并将其写入test[1]。即使它是一个循环引用(即 {test[1],test[0]}),它们仍然只是堆栈中的无意义值,所以 test[1] 的垃圾会进入 test[ 0],然后 test[0] 将进入 test[1],使它们都成为 test[1]。
      • @GraphicsMuncher:不...在评估test[1] 的预期价值时没有test[0]。你关于“堆栈上的位置”的讨论对我来说也没有多大意义。
      • @LightnessRacesinOrbit:test 的初始化应该没问题。没有test[0] 是什么意思?在 C++03 中,每个初始化程序之后都有一个序列点(自从那些被删除后我还没有查看它是怎样的),所以我很确定 test[0] 两者都存在并且在达到 test[1] 时被初始化。跨度>
      • @GManNickG:我认为说test[0] 已经被初始化器中间的逗号初始化是不公平的。
      • @LightnessRacesinOrbit: test 不是,但test[0] 是。
      【解决方案4】:

      您拥有的所有代码都是完全合法的。甚至在某些情况下,您甚至可能想要也这样做。

      【讨论】:

      • 取决于您所说的“合法”。语法正确吗?当然。定义好?没有。读取未初始化的变量是未定义的行为。这仅适用于x 初始化。 test 初始化器很好。
      • -1: There are even circumstances in which you might actually even want to do it, too. 不,绝对绝对是不是
      • 我的意思是,如果您将声明推广到其他类型而不仅仅是int,它实际上可能会完成一些事情。例如,如果数组的类型中有指向对象的指针,您可能希望第二项是另一个的副本,因此这些指针将引用相同的对象。对于第二种情况,可能存在operator = 被x 的类型重载的情况。虽然我同意没有任何理智的人愿意使用ints 来做这件事。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-14
      • 2015-09-23
      • 2013-05-19
      • 2013-05-14
      • 2012-01-20
      • 1970-01-01
      • 2018-09-15
      相关资源
      最近更新 更多