与えられた:
int i = 42;
int j = 43;
int k = 44;
変数のアドレスを見ると、それぞれが4バイトを占めることがわかります(ほとんどのプラットフォーム)。
ただし、次の点を考慮してください。
int i = 42;
int& j = i;
int k = 44;
変数i
は実際に4バイトを使用しますが、j
はnoneを使用し、k
はスタックで4バイトを使用します。
ここで何が起きてるの? j
は実行時に存在しないようです。また、関数の引数として受け取る参照はどうなりますか? mustはスタック上にいくらかのスペースを必要とします...
そして、その間、配列や参照を定義できないのはなぜですか?
int&[] arr = new int&[SIZE]; // compiler error! array of references is illegal
参照 j が出現するすべての場所で、 i のアドレスに置き換えられます。したがって、基本的に参照コンテンツアドレスはコンパイル時に解決され、実行時にポインタのように逆参照する必要はありません。
i:
void function(int& x)
{
x = 10;
}
int main()
{
int i = 5;
int& j = i;
function(j);
}
上記のコードでは、 j はmain stackでスペースを取るべきではありませんが、 function の参照 x は、スタック上で行われます。つまり、 function を j を引数として呼び出すと、 i のアドレスが function のスタックにプッシュされます。コンパイラは、メインスタックのスペースを j のために予約できます。
配列部分について、標準は言う::
C++標準8.3.2/4:
参照への参照、参照の配列、参照へのポインタがあってはなりません。
C++のリファレンスは、メモリ的にどのように見えますか?
そうではありません。 C++標準は、それがどのように実装されるべきかではなく、どのように動作するべきかについてのみ述べています。
一般的なケースでは、コンパイラーは通常、参照をポインターとして実装します。しかし、彼らは一般的に参照が指すかもしれないものについてのより多くの情報を持ち、それを最適化のために使用します。
参照の唯一の要件は、参照されるオブジェクトのエイリアスとして動作することです。したがって、コンパイラがこのコードに遭遇した場合:
int i = 42;
int& j = i;
int k = 44;
表示されるのは、「変数i
へのポインターを作成する」ではなく(コンパイラーがそれを実装することを選択する場合もあります)ではなく、「シンボルテーブルにj
はi
のエイリアスになりました。 "
コンパイラーはj
の新しい変数を作成する必要はありません。j
が今後参照されるときは常にそれを実際にスワップアウトしてi
代わりに。
参照の配列を作成することに関しては、それは役に立たず意味がないのでそれを行うことはできません。
配列を作成すると、すべての要素がデフォルトで作成されます。参照をデフォルトで作成するとはどういう意味ですか?それは何を指していますか?参照の重要な点は、別のオブジェクトを参照するためにinitializedを再実行することです。その後、再挿入することはできません。
したがって、それが可能であれば、-nothingへの参照の配列が作成されます。そして、それらを参照に変更することはできませんsomethingはすでに初期化されているためです。
アセンブリを使用してこれを説明して申し訳ありませんが、これは参照を理解するための最良の方法だと思います。
_ #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;
}
_
このコードの出力は次のようになります
_ i = 10
&i = 0xbf9e52f8
ptrToI = 0xbf9e52f8
*ptrToI = 10
&ptrToI = 0xbf9e52f4
refToI = 10
&refToI = 0xbf9e52f8
_
逆アセンブリを見てみましょう(私はこれにGDBを使用しました。ここで8、9、10はコードの行番号です)
_8 int i = 10;
0x08048698 <main()+18>: movl $0xa,-0x10(%ebp)
_
ここで_$0xa
_は、i
に割り当てている10(10進数)です。ここでの-0x10(%ebp)
は、_ebp register
_ –16(decimal)の内容を意味します。 -0x10(%ebp)
は、スタック上のi
のアドレスを指します。
_9 int *ptrToI = &i;
0x0804869f <main()+25>: lea -0x10(%ebp),%eax
0x080486a2 <main()+28>: mov %eax,-0x14(%ebp)
_
i
のアドレスをptrToI
に割り当てます。 ptrToI
は再びアドレス-0x14(%ebp)
にあるスタックにあります。つまり、ebp
– 20(10進数)です。
_10 int &refToI = i;
0x080486a5 <main()+31>: lea -0x10(%ebp),%eax
0x080486a8 <main()+34>: mov %eax,-0xc(%ebp)
_
今ここにキャッチがあります! 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
_
上記の2つの行を比較すると、驚くべき類似性がわかります。 -0xc(%ebp)
は、ユーザーがアクセスできないrefToI
の実際のアドレスです。簡単に言えば、参照を通常のポインタと考える場合、参照へのアクセスは、参照が指すアドレスで値をフェッチするようなものです。つまり、以下の2行のコードで同じ結果が得られます
_cout << "Value if i = " << *ptrToI << "\n";
cout << " Value if i = " << refToI << "\n";
_
これを比較してください
_15 cout << "ptrToI = " << ptrToI << "\n";
0x08048713 <main()+141>: mov -0x14(%ebp),%ebx
21 cout << "&refToI = " << &refToI << "\n";
0x080487fb <main()+373>: mov -0xc(%ebp),%eax
_
ここで何が起こっているのかがわかると思います。 _&refToI
_を要求すると、-0xc(%ebp)
アドレスの場所の内容が返され、-0xc(%ebp)
はrefToi
が存在する場所であり、その内容はi
のアドレスにすぎません。
最後に、なぜこの行にコメントがあるのですか?
_//cout << "*refToI = " << *refToI << "\n";
_
_*refToI
_は許可されておらず、コンパイル時にエラーが発生するためです。
実際には、参照はポインターと同等ですが、参照の使用を許可する方法に関する追加の制約により、コンパイラーはより多くの場合に「最適化」できるようになります(コンパイラーのスマートさ、最適化設定によっては)もちろん等)。
参照を初期化する構文がないため、参照の配列を定義することはできません。 C++では、初期化されていない参照は許可されません。最初の質問については、コンパイラーは不要な変数にスペースを割り当てる義務はありません。 jが別の変数を指すようにする方法はないので、これは事実上、関数のスコープ内のiの単なるエイリアスであり、コンパイラーがそれを処理する方法です。
どこか他の場所でのみ言及されているもの-コンパイラに参照用のストレージ領域を割り当てる方法:
class HasRef
{
int &r;
public:
HasRef(int &n)
: r(n) { }
};
これは、コンパイラーが単にそれをコンパイル時の別名(同じストレージの代替名)として扱う機会を否定します。
参照は、物理的な明示が必要になるまで(つまり、集合体のメンバーとして)、実際には物理的に存在しません。
おそらく参照のため、参照の配列を持つことは違法です。しかし、参照メンバーを持つ構造体/クラスの配列を作成することを妨げるものは何もありません。
私は誰かがこれすべてを言及する標準的な条項を指摘すると確信しています。
それは修正されていません-コンパイラーはケースバイケースでリファレンスを実装する方法に大きな自由があります。したがって、2番目の例では、jをiのエイリアスとして扱います。他に何も必要ありません。 refパラメーターを渡す場合、スタックオフセットを使用することもできますが、オーバーヘッドはありません。しかし、他の状況では、ポインターを使用する可能性があります。