【问题标题】:How can a variable take the address of itself within its own declaration? [duplicate]变量如何在自己的声明中获取自身的地址? [复制]
【发布时间】:2020-11-19 12:53:03
【问题描述】:

考虑一下:

void* x = &x;

printf("%p\n", x);

令人惊讶的是,它编译并运行输出:

7ffb2f7248

x 怎么可能在x 还没有创建的情况下获取自己的地址?

编辑:请注意,在这种情况下,它的分配没有歧义,清楚地说明了究竟是什么问题。

【问题讨论】:

  • 这很简单,因为当 declarator (x) 被识别时,对象类型是完整的。 void *x[] = { &x }; 更有趣,因为在完全解析 init-declarator 之前类型不完整。或void *x[] = { &x[2], 0, 0 };.
  • 引用:“令人惊讶的是,它编译并运行......”好吧,如果它没有,我会更惊讶。是什么让你认为它不应该?该代码定义了一个变量并对其进行了初始化——没有发生奇怪的事情。
  • 你不应该用 C 和 C++ 标记这样的问题;不同语言的答案是不同的。请删除一个标签,如果您想询问该语言,请输入一个单独的问题。
  • 这是一个非常有用的功能,它允许在 C:int len=10; int* array = malloc(len*sizeof*array); 中使用这种常见的数组分配模式。这样,数组的类型不必重复,当有人决定更改数组的类型时也不会产生错误。
  • Error with C++ syntax, compiler doesn't warn or error for int v = func(&v); 的副本(这个问题完全是关于地址的问题,至少自 2010 年以来就存在像 int x = x; 这样的自引用问题)。 Why does this C code compile? 有 C 的措辞。可能可以标记为仅后一个问题的副本。

标签: c++ c pointers memory language-lawyer


【解决方案1】:

C++ 2018 6.3.2 [basic.scope.pdecl] 1 说:

名称的声明点紧跟在它的后面 完整的声明符(第 11 条)及其初始化器(如果有)之前, 除非下面注明。

“下面提到的”项目不适用于此处,因此,在void* x = &x; 中,x 在第一个 x(即 声明符)之后和 @987654324 之前声明@(初始化器),所以x可以在初始化器中被引用,基本上就像代码是void *x; x = &x;

【讨论】:

  • 第一个正确答案,只是添加一些格式可能会很好地回答。
  • 我对 C 标准中的类似内容有一些模糊的回忆,但目前找不到。
【解决方案2】:

这个答案是针对 C 标记的。

表达式:

void* x = &x;

是一个init-declarator,即declarator = initializer

这可以在 N1570(草案 C 标准)的第 6.7 节中找到,6.7.6 说:

完整的声明符是不属于另一个声明符的声明符。 完整的结束 声明符是一个序列点。

6.2.1/7 说:

结构、联合和枚举标记的作用域在标记出现在声明标记的类型说明符之后开始。 ...... 任何其他标识符的范围都在其声明符完成后开始。

这些部分说明对象是在初始化之前创建的。因此,初始化器可以使用创建的对象的地址。

在第 6.3.2.3 节指针中:

指向 void 的指针可以转换为或从 指向任何对象类型的指针指向任何对象类型的指针都可以转换为指向 void 的指针,然后再返回;结果应与原始指针比较。

因此 - 由于任何指针都可以转换为 void-pointer - x 的地址可以分配给 void-pointer。

所以对于你的代码:

首先创建对象,然后对其进行初始化。

void* x = &x;
^^^^^^^ ^^^^^  
   |      |
   |      step 2: initialize object
   |
   step 1: create object, i.e. end of full declarator

对于这段代码,它与赋值几乎相同。

void* x; // Create
x = &x;  // Assignment instead of initialization
         // But in this case it works just the same

【讨论】:

    【解决方案3】:

    您将分配和初始化混为一谈。

    x 分配存储只是因为它被此声明定义为存在。初始化是存储分配之后的一个单独步骤。正因为如此,x的地址在它自己初始化的时候就可以知道了。

    (请注意,在指向的值完成初始化之前,无法安全地取消引用此类指针,但没有理由不能在初始化之前获取地址。)

    【讨论】:

    • 补充一点,在初始化过程中,&x 也是有效的,因为编译器已经看到了x 标识符并且知道x 指的是什么——它是什么类型,有多大它是,等等 - 所以& 地址操作符因此能够返回x 的存储所在的where,即使该存储的contents仍在初始化过程中。
    • @RemyLebeau 通常编译器只留下符号。链接器完成地址工作
    • @P__J__ 这将是& 运算符如何工作的实现细节。但是&x 表达式本身的解析是由编译器处理的,而不是链接器,所以很明显x 需要在编译器到达&x 时成为有效标识符。并且xx初始化 上下文中的有效标识符,已经由x声明 定义。这就是我要指出的全部内容。
    • 除了存储分配错误,这个无关紧要。在extern int x; void *y = &x; 中,我们可以引用x,即使尚未为其分配存储空间(除非事先定义)。此答案中的“因为这个”不正确。
    • 问题中没有给出这样的声明+初始化。答案针对问题中的特定声明。它并不意味着适用于可以用该语言形成的任何声明。
    【解决方案4】:

    x 还没有被创建的时候,x 怎么可能取到自己的地址呢?

    因为一个对象不需要存在就可以知道它将在哪里创建。

    【讨论】:

      【解决方案5】:

      非常简单。静态存储持续时间对象地址对于填充所有地址的链接器是已知的。

      没有优化出来的自动存储变量,其地址被使用,编译器很容易计算。

      https://godbolt.org/z/e84xM3

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-08
        • 2013-04-01
        • 2013-05-28
        • 2020-07-04
        • 1970-01-01
        相关资源
        最近更新 更多