私は以下が正しくないことを知っています:
int arr[2][3] = {}; //some array initialization here
int** ptr;
ptr = arr;
しかし、私は次の行が実際に機能することに非常に驚いています
int arr[2][3] = {}; //some array initialization here
auto ptr = arr;
int another_arr[2][3] = {}; //some array initialization here
ptr = another_arr;
コードの2番目のブロックでptrに割り当てられているタイプと、その下で何が起こったのかを誰かが説明できるでしょうか?
まあ、配列は実際にどこでも使用されるとポインタに減衰します。したがって、当然、コードスニペットでも減衰が発生します。
しかし、ポインタに減衰するのは「最も外側の」配列次元だけです。配列は行優先であるため、ポインタ型としてint (*)[3]
が使用されます。これは、2次元配列ではなく、1次元配列へのポインタです。最初の「行」を指します。
ptr
の推論をポインタ配列へのにしたい場合は、address-of演算子を使用します。
_auto ptr = &arr;
_
これで、ptr
はint(*)[2][3]
になります。
に
_auto ptr = arr;
_
arr
は、通常の方法で最初の要素へのポインタに減衰します。それはと同等です
_auto ptr = &arr[0];
_
_arr[0]
_は3つのint
sの配列であるため、ptr
はint (*)[3]
になります-_int[3]
_へのポインタ。
_another_arr
_はまったく同じ方法で減衰するため、
_ptr = another_arr;
_
割り当ての両側のタイプはint (*)[3]
であり、任意のタイプT
の_T*
_に_T*
_を割り当てることができます。
arr
自体へのポインタの型はint(*)[2][3]
です。
配列の最初の要素へのポインターではなく、配列へのポインターが必要な場合は、_&
_を使用する必要があります。
_auto ptr = &arr;
_
まず、_int arr[2][3]
_を_int **
_に割り当てられない理由を見てみましょう。視覚化を容易にするために、配列をシーケンスで初期化し、メモリ内でどのように見えるかを検討します。
_int arr[2][3] = {{1,2,3},{4,5,6}};
_
メモリには、通常の1D配列と同じように、配列データが1つのブロックとして格納されます。
_arr: [ 1, 2, 3, 4, 5, 6 ]
_
変数arr
には、このブロックの開始アドレスが含まれ、その型(_int[2][3]
_)から、コンパイラは_arr[1][0]
_のようなインデックスを「次の値を取る」という意味として解釈することを認識しています。配列内の位置(1 * 2 + 0)で」。
ただし、ポインタからポインタ(_int**
_)の場合、ポインタからポインタには単一のメモリアドレスまたはメモリアドレスの配列が含まれていると予想され、このアドレスは( an)他の単一のint値またはintの配列。配列arr
を_int **ptrptr
_にコピーしたとしましょう。メモリでは、次のようになります。
_ptrptr: [0x203F0B20, 0x203F17D4]
0x203F0B20: [ 1, 2, 3 ]
0x203F17D4: [ 4, 5, 6 ]
_
したがって、実際のint
データに加えて、配列の各行に追加のポインターを格納する必要があります。 2つのインデックスを単一の配列ルックアップに変換するのではなく、最初の配列ルックアップ(「ptrptrの2番目の値を取得してint *を取得」)を実行してから、別の配列ルックアップ(「以前に取得したint * ")が保持するアドレスの配列。
これを説明するプログラムは次のとおりです。
_#include <iostream>
int main()
{
int arr[2][3] = {{1,2,3},{4,5,6}};
std::cout << "Memory addresses for int arr[2][3]:" << std::endl;
for (int i=0; i<2; i++)
{
for (int j=0; j<3; j++)
{
std::cout << reinterpret_cast<void*>(&arr[i][j]) << ": " << arr[i][j] << std::endl;
}
}
std::cout << std::endl << "Memory addresses for int **ptrptr:" << std::endl;
int **ptrptr = new int*[2];
for (int i=0; i<2; i++)
{
ptrptr[i] = new int[3];
for (int j=0; j<3; j++)
{
ptrptr[i][j] = arr[i][j];
std::cout << reinterpret_cast<void*>(&ptrptr[i][j]) << ": " << ptrptr[i][j] << std::endl;
}
}
// Cleanup
for (int i=0; i<2; i++)
{
delete[] ptrptr[i];
ptrptr[i] = nullptr;
}
delete[] ptrptr;
ptrptr = nullptr;
return 0;
}
_
出力:
_Memory addresses for int arr[2][3]:
0x7ecd3ccc0260: 1
0x7ecd3ccc0264: 2
0x7ecd3ccc0268: 3
0x7ecd3ccc026c: 4
0x7ecd3ccc0270: 5
0x7ecd3ccc0274: 6
Memory addresses for int **ptrptr:
0x38a1a70: 1
0x38a1a74: 2
0x38a1a78: 3
0x38a1a90: 4
0x38a1a94: 5
0x38a1a98: 6
_
arr
のメモリアドレスは常に4バイト増加しますが、ptrptr
の場合は値3と4の間で24バイトのジャンプがあることに注意してください。
単純な割り当てでは、タイプ_int **
_に必要なポインター間構造を作成できません。そのため、上記のプログラムでループが必要でした。最善の方法は、_int[2][3]
_型をその配列の行へのポインター、つまりint (*)[3]
に減衰させることです。それがあなたの_auto ptr = arr;
_が最終的になるものです。
タイプは何ですか[...]
コンパイラに式のタイプを教えてもらうようにすでに試みましたか?
int main()
{
int arr[2][3] = {{0,1,2}, {3,4,5}}; // <-- direct complete initialized here
auto ptr = arr; // <-- address assignment only
cout << "arr: " << typeid(arr).name() << endl;
cout << "ptr: " << typeid(ptr).name() << endl;
return 0;
}
私はその出力を告白しなければなりません
arr: A2_A3_i
ptr: PA3_i
一見(他のいくつかの言語と比較して)あまり読みにくいように見えますが、疑わしい場合は役立つかもしれません。とてもコンパクトですが、すぐに慣れるかもしれません。エンコーディングはコンパイラに依存します。gccを使用している場合は、 第29章。デマングリング を読んでその方法を理解してください。
いくつかのsimple_cpp_name
この初歩的なハックのように機能する
#include <typeinfo>
#include <cxxabi.h>
#include <stdlib.h>
#include <string>
std::string simple_cpp_name(const std::type_info& ti)
{
/// simplified code extracted from "Chapter 29. Demangling"
/// https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html
char* realname = abi::__cxa_demangle(ti.name(), 0, 0, 0);
std::string name = realname;
free(realname);
return name;
}
auto &rfa = arr;
はrfa
をarr
と同じタイプのint [2][3]
。