0

これは C++ 自体よりもコンピューター アーキテクチャに関する問題かもしれませんが、C++ のような言語でポインターを使用せずに理論的に参照渡しを実装できるかどうか疑問に思っていました。

以下に、同様の機能と構造を持つコードの例を 3 つ示します。

//Version 1: Uses global variable

#include <iostream>

int global_var = 0;

int increment(void)      {return ++global_var;}

int main(void)
{
    while (global_var < 100)
        printf("Counter: %i\n", increment());
    return 0;
}

//Version 2: Uses pass-by-pointer

#include <iostream>

int increment(int* ptr)   {return ++(*ptr);}

int main(void)
{
    int local_var = 0;
    while (local_var < 100)
        printf("Counter: %i\n", increment(&local_var));
    return 0;
}

//Version 3: Uses pass-by-reference

#include <iostream>

int increment(int& ref)   {return ++ref;}

int main(void)
{
    int local_var = 0;
    while (local_var < 100)
        printf("Counter: %i\n", increment(local_var));
    return 0;
}

私の推測では、最初のバージョンのインクリメント関数は、ポインターなしでグローバル変数に直接アクセスします。2 番目のバージョンは、ローカル変数を指す関数のスタック フレームにポインターを割り当て、間接的にアクセスします。クイック検索から、3 番目のバージョンは 2 番目のバージョンとまったく同じように (とにかく最適化する前に) 実装されているようです。出典:リファレンスは内部でどのように実装されていますか?

しかし、理論上(または実際には、コンパイラによる最適化後)、3 番目の関数は、ポインターなしで独自のスタック フレーム外のローカル変数に直接アクセスできますか? それとも、メモリ内の場所が静的であるため、その動作はグローバル変数に限定されますか?

ポインターの作成と逆参照には、少しの時間とメモリが必要だと思うので、これを尋ねます。十数回の参照を渡す深い再帰関数などでは、その時間とメモリが加算される可能性があります。

PS インライン関数についても具体的に言及する必要があります。これらの関数は新しいスタック フレームを生成することすらありません。つまり、バージョン 2 と 3 のアセンブリ コードは、関数がinline int increment(int*)との場合に異なりますinline int increment(int&)か? または、その場合、コンパイラはポインターを最適化して削除しますか?

4

2 に答える 2

1

これはコンパイラによって異なる方法で処理される可能性があるため、msvc++ が次のコードを処理する方法の例を示します。

#include <iostream>

__declspec(noinline) int Increament(int* p)
{
    std::cout << "Increament Pointer called" << std::endl;
    ++*p;
    return *p;
}

__declspec(noinline) int Increament(int& p)
{
    std::cout << "Increament Reference called" << std::endl;
    ++p;
    return p;
}

int main()
{
    int x = 10;
    Increament(x);
    Increament(&x);


    std::cin.get();
    return 0;
}

ご覧のとおり、どちらのバージョンの Increament() もまったく同じコードを生成し、x の実効アドレスをレジスタ eax にロードし、そのアドレスをスタックにプッシュします。

int x = 10;
00A52598  mov         dword ptr [x],0Ah  
    Increament(x);
00A5259F  lea         eax,[x]  
    Increament(x);
00A525A2  push        eax  
00A525A3  call        Increament (0A5135Ch)  
00A525A8  add         esp,4  
    Increament(&x);
00A525AB  lea         eax,[x]  
00A525AE  push        eax  
00A525AF  call        Increament (0A51357h)  
00A525B4  add         esp,4  

残りの質問については、コンパイラーは好きなことを自由に行うことができ、結果は互いに異なります。

これを投稿した理由は、asm には参照がなく、参照はコンパイラに関する限りポインターとして扱われ、実際には参照は c++ の設計が少し異なる制限付きのポインターであることを理解してもらうためです。

コメントのいくつかの質問による更新:

グローバル変数を使用すると、異なるアセンブリ出力が生成されますか

1) コードの最初の例では、global_var がグローバルで初期化され、代わりにデータ セグメントに格納されるため、異なるアセンブリが生成されます。

見てみましょう:

#include <iostream>

int global_var = 50;

__declspec(noinline) int increment(void)
{ 
    ++global_var;
    return global_var;
}

int main(void)
{
    increment();

    std::cin.get();
    return 0;
}

関数の次のアセンブリが生成されます。ここでは何もプッシュされていないことに注意してください。

00A61000  mov         eax,dword ptr ds:[00A63018h]  
00A61005  inc         eax  
00A61006  mov         dword ptr ds:[00A63018h],eax 

0x00A63018 はメモリ内の global_var のアドレスであり、その値は eax に格納され、インクリメントされて古いメモリ位置に復元されます。

グローバル変数と同じ方法で参照を実装することは基本的に不可能ですか (つまり、ポインターなしでそれらにアクセスします)

2) あなたの質問がわかりません。ルールによって何かを直接指す必要があるグローバル参照を持つことができます。たとえば、別の初期化された変数は、50 のような値を保持することを除いて同じように動作し、他のグローバル変数へのアドレスを保持します。

つまり、ポインタなしでそれらにアクセスします

この部分は私を混乱させるものであり、参照について話すときは意味がありません. C ++では不可能なポインターによる参照へのアクセスはありません。参照のアドレスを取得することさえできません。

于 2015-07-27T02:11:01.690 に答える