Kar*_*ing 8 c++ compiler-construction pointers reference
我正在努力学习C++编译器如何处理引用和指针,以便为下一学期的编译器类做准备.我对编译器如何处理C++中的引用特别感兴趣.
该标准指定引用是"别名",但我不确切地知道它在编译器级别的含义.我有两个理论:
非引用变量在符号表中有一个条目.当创建对该变量的引用时,编译器只是创建另一个词汇,该词汇"指向"符号表中的完全相同的条目(而不是内存中的非引用变量的位置).
当创建对该变量的引用时,编译器会在内存中创建指向该变量位置的指针.解析语言的上下文时会处理对引用的限制(无空值等).换句话说,引用是解除引用指针的"语法糖".
据我所知,这两种解决方案都会产生"别名".编译器是使用一个而不是另一个?还是依赖于编译器?
顺便说一句,我知道在机器语言级别,两者都是"指针"(除了整数之外的所有东西都是机器级别的"指针").我对生成机器代码之前编译器的作用感兴趣.
编辑:我很好奇的部分原因是因为PHP使用方法#1,我想知道C++编译器是否以相同的方式工作.Java肯定不使用方法#1,它们的"引用"实际上是解引用的指针; 请参阅Scott Stanchfield 撰写的这篇文章.
我将尝试解释g ++编译器如何实现引用.
#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 << "refToNum = " << refToI << "\n";
//cout << "*refToNum = " << *refToI << "\n";
cout << "&refToNum = " << &refToI << "\n";
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这段代码的输出是这样的
i = 10
&i = 0xbf9e52f8
ptrToI = 0xbf9e52f8
*ptrToI = 10
&ptrToI = 0xbf9e52f4
refToNum = 10
&refToNum = 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)
指定的地址i来ptrToI.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)refToNum
所以; 引用确实占用了内存.在这种情况下,它是堆栈内存,因为我们已将其分配为局部变量.它占用了多少内存?指针占据了很多.
现在让我们看看我们如何访问引用和指针.为简单起见,我只展示了部分组装片段
16 cout << "*ptrToI = " << *ptrToI << "\n";
0x08048746 <main()+192>: mov -0x14(%ebp),%eax
0x08048749 <main()+195>: mov (%eax),%ebx
19 cout << "refToNum = " << 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 << "&refToNum = " << &refToI << "\n";
0x080487fb <main()+373>: mov -0xc(%ebp),%eax
Run Code Online (Sandbox Code Playgroud)
我猜你能发现这里发生了什么.如果您要求&refToI,-0xc(%ebp)地址位置的内容将 被返回,并且-0xc(%ebp)位于其中refToi,其内容只是地址i.
最后一件事,为什么这一行被评论了?
//cout << "*refToNum = " << *refToI << "\n";
Run Code Online (Sandbox Code Playgroud)
因为*refToI不允许,它会给你一个编译时错误.
Tho*_*ews -2
理解指针和引用与实现它们的代码有很大不同。
我建议你学习如何正确使用它们并专注于编译器理论的核心。如果没有指针、引用和继承的概念,基本的编译器理论课程就已经足够困难了。指针和参考资料留给更高级的课程。
简而言之:可以时使用引用,必须时使用指针。
编辑1:
编译器可以以任何他们想要的方式实现引用和指针,只要它们的语法和语义按照语言规范运行即可。
一个简单的实现是将引用视为具有附加属性的指针。
内存中的所有内容都有一个位置,即地址。编译器可能必须使用内部指针从内存加载到寄存器并将寄存器内容存储到内存中。因此,要引用内存中的变量,无论是通过指针、引用还是别名,编译器都需要变量的地址。(这不包括以不同方式处理的寄存器变量。)因此,使用指针作为引用或别名可以节省一些编码。
| 归档时间: |
|
| 查看次数: |
1281 次 |
| 最近记录: |