【问题标题】:Why references to the same constant take distinct memory space in C++?为什么对相同常量的引用在 C++ 中占用不同的内存空间?
【发布时间】:2019-04-24 13:59:19
【问题描述】:

我是 C++ 中引用概念的新手,我有一个关于引用纯数字常量的内存分配的问题。 (我首先要检查的另一件事是我怀疑我经常遇到的 const reference 表示 reference to const,但我不确定。)

这是我的testing on ideone.com:

#include <stdio.h>

int main() {

    const int r0 = 123;
    const int &r1 = 123;
    const int &r2 = 123;
    const int &r3 = r2;

    printf("%p\n", (void *)&r0);
    printf("%p\n", (void *)&r1);
    printf("%p\n", (void *)&r2);
    printf("%p\n", (void *)&r3);

    return 0;
}

结果:

0x7ffee3bd74c4
0x7ffee3bd74c8
0x7ffee3bd74cc
0x7ffee3bd74cc

r2 与r3 相同的原因从answer - How does a C++ reference look, memory-wise? 中可以清楚地看出,它说这取决于编译器。但我在想为什么编译器不也使r0,r1,r2 都一样,因为它们都具有相同的纯常量值123。 (如果没有错误搜索,则称为prvalue)

作为说明:在此站点上进行了一些搜索后,我找到了一个最相关的question - but in python。虽然语言不同,但我认为这个想法应该是相同/相似的:从链接中,如果我的程序是用 python 编写的,那么内存空间中将只有一个 123 以节省空间。

我读过的其他一些答案:

  1. C++ do references occupy memory:这个答案表明,如果有必要,那么 int &amp;x 被实现为 *(pointer_to_x)。
  2. How does a C++ reference look, memory-wise?:这个答案表明编译器会尽力节省空间。

【问题讨论】:

  • 我怀疑答案是因为您在没有开启任何优化的情况下运行了代码。
  • @MooingDuck:在我睡着之前,我正在(重新)考虑 C++ 是否会像 python 一样进行优化,然后现在我醒来并意识到它应该是相反/相反的问题或 C++ 应该更好地处理它。当 C++ 这样做时,规范的方式/机制是什么?
  • 默认情况下,编译器会生成未优化的构建,这使得单步调试和调试远变得更容易。然而,每个编译器也有一个你可以传递的标志,告诉它构建一个优化的构建,这将缩小它并使其快速和小。你是如何编译你的代码的?
  • @MooingDuck:我刚刚在ideone.com 上单击了运行,我确实尝试在那里找到所谓的优化选项,但似乎他们没有。因为我很少考虑优化,所以我对优化的了解不多。
  • ptr_user7813604 coliru.stacked-crooked.com 允许您将 -O0 到 -O3 标志传递给编译器,让您看到各种结果。 (-O0 是默认不优化,-O3 是最大优化)

标签: c++ reference


【解决方案1】:

三个声明语句:

const int &r1 = 123;
const int &r2 = 123;
const int &r3 = r2;

将初始化 3 个生命周期延长的临时对象,使其等于它们各自变量的范围。现在,有一条语言规则说:

任何两个具有重叠生命周期的对象(不是位域) 保证有不同的地址,除非其中一个是 另一个的子对象或为另一个提供存储,或者如果它们是 同一完整对象内的不同类型的子对象,以及一个 其中一个是零大小的基数。

由于引用绑定到 3 个不同的临时对象,因此您无法在重叠地址上观察这些对象。

有趣的是,As-if 规则可能允许程序在同一地址分配所有三个临时对象,但前提是您的编译器和链接器理论上可以证明您的程序永远无法观察到这些对象分配在同一地址.在您的示例中,这是不可行的,因为您打印了对象的地址。

【讨论】:

  • 这让我想起了量子力学,因为我的观察是实验的一部分。顺便说一句,我很难理解这种语言规则,...保证...为了什么?
  • @ptr_user7813604 这是一个哲学问题,您需要深入研究 C++ 草案和提案才能找到答案。一个可能的原因是对象标识可用于计算 - 如果它们引用的对象相同,则您永远无法在没有名称的情况下区分 r1 和 r2。
  • 好的,我喜欢这个理由,我会进一步归纳,编译器只能在理论上进行优化,几乎无法区分。顺便说一句,我喜欢哲学问题,因为它不一定有答案。
  • 我不明白您的 ["] 由于引用绑定到 3 个不同的临时对象,因此您无法在 重叠地址 上观察这些对象。[." ],这是否意味着一些有相同的地址?
  • @ptr_user7813604 “重叠”不是指第一个字节的地址,而是指对象占用的地址范围。这个限制意味着一个字节不能被多个对象占用(除了摘录中所说的)。
【解决方案2】:

文字不是对象。引用不指文字。使用字面量初始化引用时,会创建一个临时对象,并且临时对象的生命周期与引用的生命周期绑定。

对象(一个局部变量,两个临时对象)是独立且不同的对象,尽管具有相同的值。由于它们是独立的,因此它们占用不同的内存位置。标准规定了这一点,这使得根据对象的内存地址识别和区分对象成为可能。

【讨论】:

    【解决方案3】:

    您的123 不是“常数”。相反,它是一个文字。文字形成了一个纯右值的表达式(即用文字给定的值初始化的临时对象)。当您将该表达式绑定到引用时,该对象的生命周期会延长到引用的生命周期,但这里的重点是每个此类对象都是一个不同的对象,因此具有不同的地址。

    如果您愿意,文本字符串“123”提供了如何创建对象的规则,但它本身并不是对象。您可以重写您的代码以使其更明确:

    const int & r = int(123);   // temporary of type "int" and value "123"
    

    (在 C++ 中没有单一的“常量”这样的东西。有很多东西以某种方式是常量,但它们都需要更详细的考虑。)

    【讨论】:

    • 那么既然const 只是编译时检查它与常量无关?
    • 纯右值是一个表达式,而不是一个临时对象。此外,在某个阶段,可能有也可能没有临时对象物化为纯右值。引用绑定会导致临时实现。
    • @ptr_user7813604:我宁愿说“常数”这个词太模糊,以至于这个问题没有意义。在 C++ 中,很多东西可以以不同的方式保持不变(例如类型、表达式、初始化)。
    • @ptr_user7813604:如果您将文字视为源代码的一部分,而将对象创建视为运行时行为的一部分,也许会有所帮助?源代码不会创建对象,只有 execution 会。源代码(在本例中为文字)决定了如何 对象在运行时被创建。临时对象就其本质而言是不同的;临时对象没有别名。没有两个不相关的表达式表示同一个临时对象(直到您开始将临时对象绑定到某个引用)。这就是语言的设计方式。
    • @ptr_user7813604:我想说优化是一个不相关的话题,不会影响当前的问题,这完全是关于对象模型和语言中表达式的性质。所有这些都是 C++ 设计的核心部分。最终,直接的答案总是“因为这就是语言的工作方式”,所以也许更令人满意的问题是为什么语言是这样设计的。临时对象在某些全局缓存中不是非临时对象,它们是真正的临时对象,就在需要它们的地方(新的、唯一的)创建。
    猜你喜欢
    • 2021-05-16
    • 2023-03-06
    • 2015-11-27
    • 2021-09-17
    • 2019-01-02
    • 1970-01-01
    • 2021-08-25
    • 2020-05-09
    • 2021-01-21
    相关资源
    最近更新 更多