内存方面的C++引用如何?

Yuv*_*dam 42 c++ memory-management reference

鉴于:

int i = 42;
int j = 43;
int k = 44;
Run Code Online (Sandbox Code Playgroud)

通过查看变量地址,我们知道每个地址占用4个字节(在大多数平台上).

但考虑到:

int i = 42;
int& j = i;
int k = 44;
Run Code Online (Sandbox Code Playgroud)

我们将看到,变量i确实需要4个字节,但j需要没有k再次发生堆栈上的4个字节.

这里发生了什么?看起来j在运行时根本就不存在.那么我作为函数参数收到的引用呢?这必须在堆栈上占用一些空间......

虽然我们在这里 - 为什么我不能定义数组或引用?

int&[] arr = new int&[SIZE]; // compiler error! array of references is illegal
Run Code Online (Sandbox Code Playgroud)

Ara*_*raK 49

在遇到引用j的任何地方,它都被替换为i的地址.所以基本上参考内容地址在编译时被解析,并且不需要像运行时的​​指针一样取消引用它.

只是为了澄清我的意思是我的地址:

void function(int& x)
{
    x = 10;
}

int main()
{
    int i = 5;
    int& j = i;

    function(j);
}
Run Code Online (Sandbox Code Playgroud)

在上面的代码,Ĵ不应该采取的上空间主堆叠,但参考X函数将其堆栈上的一个地方.这意味着当使用j作为参数调用函数时,将在函数堆栈上推送的i的地址.编译器可以而且不应该在主堆栈上j保留空间.

对于阵列部分标准说::

C++标准8.3.2/4:

不应引用引用,不引用引用数组,也不引用引用指针.

为什么引用数组是非法的?

  • 您可以将引用变量视为另一个变量的同义词.它不需要更多存储,因为它不是真正的"东西",只是现有东西的新名称.另一方面,引用参数本质上是一个指针值,需要指针的内存. (4认同)
  • 重点是它不仅仅是"我的地址".它是*的另一个名字*.在某些情况下,这个"其他名称"必须通过存储i的地址来实现为指针,该地址占用几个字节,但这是一个实现细节,而不是参考概念的一部分. (4认同)
  • 它不占用任何堆栈,因为编译器*知道*的地址.它不需要存储它. (3认同)

jal*_*alf 38

内存方面的C++引用如何?

它没有.C++标准只说明它应该如何表现,而不是它应该如何实现.

在一般情况下,编译器通常将引用实现为指针.但是它们通常有更多关于引用可能指向的信息,并将其用于优化.

请记住,引用的唯一要求是它表现为引用对象的别名.因此,如果编译器遇到此代码:

int i = 42;
int& j = i;
int k = 44;
Run Code Online (Sandbox Code Playgroud)

它看到的不是"创建指向变量的指针i"(尽管这是编译器在某些情况下可能选择实现它的方式),而是"在符号表中做一个j现在是别名的注释i".

编译器不必为其创建新变量j,只需要记住,j从现在开始引用时,它应该真正交换出来并使用i.

至于创建一个引用数组,你不能这样做,因为它没用,毫无意义.

创建数组时,所有元素都是默认构造的.默认构造引用意味着什么?它指向什么?引用中的重点是它们被初始化为引用另一个对象,之后它们无法重新定位.

因此,如果可以完成,您最终会得到一系列对任何内容的引用.并且你无法改变它们以引用某些东西,因为它们已经被初始化了.

  • +1.在ISO C++中,"引用不是对象".因此,它不需要任何内存表示.这只是一个别名. (4认同)

Pra*_*ane 11

很抱歉使用汇编来解释这一点,但我认为这是了解参考资料的最佳方式.

    #include <iostream>

    using namespace std;

    int main()
    {
        int i = 10;
        int *ptrToI = &i;
        int &refToI = i;

        cout << "i = " << i << "\n";
        cout << "&i = " << &i << "\n";

        cout << "ptrToI = " << ptrToI << "\n";
        cout << "*ptrToI = " << *ptrToI << "\n";
        cout << "&ptrToI = " << &ptrToI << "\n";

        cout << "refToI = " << refToI << "\n";
        //cout << "*refToI = " << *refToI << "\n";
        cout << "&refToI = " << &refToI << "\n";

        return 0;
    }
Run Code Online (Sandbox Code Playgroud)

这段代码的输出是这样的

    i = 10
    &i = 0xbf9e52f8
    ptrToI = 0xbf9e52f8
    *ptrToI = 10
    &ptrToI = 0xbf9e52f4
    refToI = 10
    &refToI = 0xbf9e52f8
Run Code Online (Sandbox Code Playgroud)

让我们看看反汇编(我为此使用了GDB.这里是8,9和10是代码的行号)

8           int i = 10;
0x08048698 <main()+18>: movl   $0xa,-0x10(%ebp)
Run Code Online (Sandbox Code Playgroud)

$0xa是我们分配给的10(十进制)i.-0x10(%ebp)这里表示ebp register-16(十进制)的内容. -0x10(%ebp)指向i堆栈的地址.

9           int *ptrToI = &i;
0x0804869f <main()+25>: lea    -0x10(%ebp),%eax
0x080486a2 <main()+28>: mov    %eax,-0x14(%ebp)
Run Code Online (Sandbox Code Playgroud)

指定的地址iptrToI.ptrToI再次位于地址的堆栈上-0x14(%ebp),即ebp- 20(十进制).

10          int &refToI = i;
0x080486a5 <main()+31>: lea    -0x10(%ebp),%eax
0x080486a8 <main()+34>: mov    %eax,-0xc(%ebp)
Run Code Online (Sandbox Code Playgroud)

现在抓住了!比较第9行和第10行的反汇编,您将观察到,在行号10中被-0x14(%ebp)替换为地址.它在堆栈上分配.但是您永远无法从代码中获取此地址,因为您无需知道地址.-0xc(%ebp)-0xc(%ebp)refToI

所以; 引用确实占用了内存.在这种情况下,它是堆栈内存,因为我们已将其分配为局部变量.它占用了多少内存?指针占据了很多.

现在让我们看看我们如何访问引用和指针.为简单起见,我只展示了部分组装片段

16          cout << "*ptrToI = " << *ptrToI << "\n";
0x08048746 <main()+192>:        mov    -0x14(%ebp),%eax
0x08048749 <main()+195>:        mov    (%eax),%ebx
19          cout << "refToI = " << refToI << "\n";
0x080487b0 <main()+298>:        mov    -0xc(%ebp),%eax
0x080487b3 <main()+301>:        mov    (%eax),%ebx
Run Code Online (Sandbox Code Playgroud)

现在比较以上两行,你会看到惊人的相似性. -0xc(%ebp)refToI您永远无法访问的实际地址.简单来说,如果您将引用视为普通指针,那么访问引用就像获取引用指向的地址处的值一样.这意味着以下两行代码将为您提供相同的结果

cout << "Value if i = " << *ptrToI << "\n";
cout << " Value if i = " << refToI << "\n";
Run Code Online (Sandbox Code Playgroud)

现在比较一下

15          cout << "ptrToI = " << ptrToI << "\n";
0x08048713 <main()+141>:        mov    -0x14(%ebp),%ebx
21          cout << "&refToI = " << &refToI << "\n";
0x080487fb <main()+373>:        mov    -0xc(%ebp),%eax
Run Code Online (Sandbox Code Playgroud)

我猜你能发现这里发生了什么.如果您要求&refToI,-0xc(%ebp)地址位置的内容将 被返回,并且-0xc(%ebp)位于其中refToi,其内容只是地址i.

最后一件事,为什么这一行被评论了?

//cout << "*refToI = " << *refToI << "\n";
Run Code Online (Sandbox Code Playgroud)

因为*refToI不允许,它会给你一个编译时错误.

  • 在这个特定的代码中,有什么理由让 ref 占用内存而不是使用别名吗?很高兴看到编译器版本和编译选项。 (2认同)

Ale*_*lli 9

在实践中,引用等同于指针,除了允许使用引用的额外约束可以允许编译器在更多情况下"优化它"(取决于编译器的智能程度,其优化设置,当然等等).


Pet*_*man 8

您无法定义引用数组,因为没有语法来初始化它们.C++不允许未初始化的引用.至于你的第一个问题,编译器没有义务为不必要的变量分配空间.没有办法让j指向另一个变量,因此它实际上只是函数范围中i的别名,而这就是编译器对待它的方式.


Dan*_*ker 6

在其他地方只提到的东西 - 如何让编译器将一些存储空间用于引用:

class HasRef
{
    int &r;

public:
    HasRef(int &n)
        : r(n) { }
};
Run Code Online (Sandbox Code Playgroud)

这使得编译器无法简单地将其视为编译时别名(同一存储的替代名称).