引用变量存储在哪里?

8

我知道引用不会占用任何内存,它将指向与其引用的内存位置相同的位置。 例如:

int i=10;
int &r = a;

假设 i 指向内存位置1000,那么在这种情况下,r 也将指向内存位置1000。 但在C++中,每当我们声明一个变量时,它都会被存储在某个内存位置。 在这种情况下,r 指向某个位置,但应该通过某种内部表示方式存储在内存中。 提前致谢。

2
如果r是一个变量,那么r的地址就是&r。这不是引用,C不是C++。所以如果r = &a,&r表示存储a地址的地址。 - Tim
引用变量存储在哪里是未指定的。 - uh oh somebody needs a pupper
4个回答

12

这一点没有具体说明,但是有很好的理由。真正的答案是:它取决于引用的情况。它可以表示为普通指针,或者根本不存在。

如果您有一个具有自动存储期限制的函数本地引用,例如此r

void foo()
{
  int x[4] = {0, 1, 2, 3};
  int &r = x[1];
  // more code
}

如果引用不是“持久的”或对其他翻译单元可见(例如数据成员或全局变量),那么它可能根本不占用任何空间。编译器将简单地将r的所有使用视为x[1]的别名,并直接访问该int。请注意,这种别名样式的引用也可能来自函数内联。

另一方面,如果引用是“持久的”或对其他翻译单元可见(例如数据成员或全局变量),则必须占用一些空间并存储在某个位置。在这种情况下,它很可能被表示为指针,并且使用它的代码将被编译为解引用该指针。

理论上,其他选项也可能存在(例如查找表),但我认为这些选项不会被任何现实世界的编译器所使用。


5

我知道引用不占用任何内存。

并不完全正确。一个引用是否有存储空间是未指定的。它可能有,也可能没有。在这个特定的例子中,它不需要存储空间,因此在典型的实现中,它不使用任何存储空间。

它将指向与其引用相同的内存位置

这听起来像是自我证明或者只是一种误解,具体取决于您对“指向”一词的理解。引用“引用”对象或者“绑定”到对象。您可以将其视为变量名称的别名。变量名称也不使用任何内存。

在这种情况下,r指向某个位置,但它应该被存储在内存中

它不需要被存储在内存中。请考虑以下代码:

int i=10;
int &r = a;
int j = r * 3;

编译器可以将r * 3解释为i * 3,就好像你一开始就这样写的一样。所引用对象的位置在编译时已知,因此无需在运行时存储地址。
但是,在其他情况下可能需要存储。例如:考虑具有外部链接的非内联函数的引用参数。当编译函数时,所引用的对象是未知的,因此必须在运行时通过内存传递一些信息。

在引用使用内部表示时只使用const指针

这是不正确的。内部表示可能使用指针,也可能使用其他内容,或者可能根本不需要使用任何东西。
因此,简洁地回答:

引用变量存储在哪里?

这是未指定的。可能是没有地方,也可能是某个地方。

1

在引用的内部表示中只使用const指针

你从哪里听到这个的?那是不正确的。

标准没有规定如何实现引用。

以下是过于简化的说明

最常见的情况是,编译器在内部有一个符号表,其中存储了所有关于变量的信息。

我们来看一个简单的例子:

int a;
a = 100;

如果编译器可以像这样进行操作(为了简单起见,让a的地址成为已知的固定地址),那么就可以得到以下结果。
| identifier | type | address  |
|------------|------|----------|
| a          | int  | 0xFF00A4 |

然后它可以将C ++代码翻译成类似于这样的内容:
mov 0xFF00A4, 100

让我们在其中添加一个参考文献:
int a;
a = 100;
int& ra = 300;

编译器拥有的符号表:
| identifier | type | address  |
|------------|------|----------|
| a          | int  | 0xFF00A4 |
| ra         | int& | 0xFF00A4 |

或者:

| identifier | type | address  | alias |
|------------|------|----------|-------|
| a          | int  | 0xFF00A4 | -     |
| ra         | int& | -        | a     |

因此,它可以生成如下代码:
mov 0xFF00A4, 100
mov 0xFF00A4, 300

当您在函数中有一个引用参数时,内部传递的是一个指针。

这并不是特定于引用的;编译器通常会对指针执行这种优化,一旦所有内容都进入SSA形式,实际上没有任何区别。 - Matteo Italia

0

标准规定:

未指定引用是否需要存储(3.7)。

(C++11,[dcl.ref] ¶4)

这意味着编译器可以根据情况自由选择是否需要任何存储。

现在,让人们说什么都可以,但引用归结为指针的语法糖(即使在编译器级别,在所有主要的 C++ 编译器中,“引用”概念几乎在前端之后立即消失)。因此,在一般情况下,它们可能需要在内存中占用空间,就像指针一样。但是,在像您这样的情况下(局部引用),编译器应该能够了解它们并根据需要对其进行优化。

请注意,这并不是引用的专属 - 编译器甚至可以通过指针执行相同类型的优化(一旦您的代码进入 SSA 形式,引用不能重新设置也没有什么特别的地方)。

这个:

int glob;

void direct() {
    glob = 16; 
}

void through_reference() {
    int &a = glob;
    a = 16;
}

void through_pointer() {
    int *a = &glob;
    *a = 16;
}

归根结底,在我尝试过的任何编译器上,代码始终相同,在gcc.godbolt.org上也是如此- 示例

direct():
        mov     DWORD PTR glob[rip], 16
        ret
through_reference():
        mov     DWORD PTR glob[rip], 16
        ret
through_pointer():
        mov     DWORD PTR glob[rip], 16
        ret
glob:
        .zero   4

另一方面,当涉及到结构时,情况会变得有些棘手。在这里,编译器被允许摆脱结构实际布局之外的引用(如果它能够重构它们实际指向的内容),而对于指针,情况可能会更加复杂(省略它们将破坏标准布局类的东西)。
实际上,在任何真实世界的编译器中,我从未看到过这种优化的实现。使用gcc或MSVC或Clang等工具,即使是最简单的情况,结构大小也将相等。

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