二次元配列へのポインタを作成する

ここでは、配列の最初の要素へのポインターを作成します

uint8_t (*matrix_ptr)[20] = l_matrix;

typedef を使用すると、見た目がすっきりします

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

その後、再び人生を楽しむことができます:)

matrix_ptr[0][1] = ...;

C のポインター/配列の世界に注意してください。これについては多くの混乱があります。

編集

コメントフィールドが短すぎてそこで実行できないため、ここで他の回答のいくつかを確認します。複数の代替案が提案されましたが、それらがどのように動作するかは示されていませんでした。これが彼らのやり方です

uint8_t (*matrix_ptr)[][20] = l_matrix;

エラーを修正し、アドレスの演算子 & を追加すると 次のスニペットのように

uint8_t (*matrix_ptr)[][20] = &l_matrix;

次に、20 uint8_t の型配列の要素の不完全な配列型へのポインターを作成します。ポインターは配列の配列へのポインターであるため、

でアクセスする必要があります。
(*matrix_ptr)[0][1] = ...;

また、これは不完全な配列へのポインタであるため、できません ショートカットとして行う

matrix_ptr[0][0][1] = ...;

インデックスを作成するには、要素の型のサイズを知る必要があるためです (インデックスを作成すると、ポインターに整数が追加されるため、不完全な型では機能しません)。これは C でのみ機能することに注意してください 、なぜなら T[]T[N] 互換性のあるタイプです。 C++ には 互換性のある型 という概念がありません T[] であるため、そのコードは拒否されます。 および T[10] 異なるタイプです。

次の代替方法はまったく機能しません。配列を 1 次元配列として表示すると、配列の要素の型が not であるからです。 uint8_t 、しかし uint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

以下は良い代替手段です

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

でアクセスします
(*matrix_ptr)[0][1] = ...;
matrix_ptr[0][0][1] = ...; // also possible now

外側の寸法のサイズが保持されるという利点があります。これで sizeof を適用できます

sizeof (*matrix_ptr) == sizeof(uint8_t) * 10 * 20

配列内のアイテムが連続して格納されるという事実を利用するもう1つの答えがあります

uint8_t *matrix_ptr = l_matrix[0];

現在、正式には、2 次元配列の最初の要素の要素にのみアクセスできます。つまり、次の条件が成立します

matrix_ptr[0] = ...; // valid
matrix_ptr[19] = ...; // valid

matrix_ptr[20] = ...; // undefined behavior
matrix_ptr[10*20-1] = ...; // undefined behavior

おそらく 10*20-1 まで動作することに気付くでしょう。 、しかし、エイリアス分析やその他の積極的な最適化を行うと、一部のコンパイラはそのコードを壊す可能性があると仮定する可能性があります.そうは言っても、失敗するコンパイラに遭遇したことはありません(ただし、実際のコードでその手法を使用したことはありません)、C FAQでさえその手法が含まれています(そのUB'nessに関する警告付き) )、配列タイプを変更できない場合、これはあなたを救う最後のオプションです:)


完全に これを理解する必要があります 次の概念を理解してください:

配列はポインターではありません!

まず第一に (十分説教されています)、配列はポインターではありません。 .代わりに、ほとんどの場合、最初の要素へのアドレスに「減衰」し、ポインタに割り当てることができます:

int a[] = {1, 2, 3};

int *p = a; // p now points to a[0]

配列の内容をすべてコピーせずにアクセスできるように、このように機能すると思います。これは単なる配列型の動作であり、それらが同じものであることを意味するものではありません.

多次元配列

多次元配列は、コンパイラ/マシンが理解して操作できるようにメモリを「分割」する方法にすぎません。

たとえば、int a[4][3][5] =整数サイズのメモリの 4*3*5 (60) 'チャンク' を含む配列。

int a[4][3][5] を使用する利点 対プレーン int b[60] '分割' され (必要に応じて 'チャンク' の操作が容易になります)、プログラムは境界チェックを実行できるようになりました。

実際、int a[4][3][5] 正確に保存されます int b[60] のように メモリ内 - のみ 違いは、プログラムが特定のサイズの別個のエンティティであるかのように管理することです (具体的には、5 つのグループが 3 つのグループの 4 つのグループ)。

注意:両方の int a[4][3][5]int b[60] メモリ内では同じです。唯一の違いは、アプリケーション/コンパイラによる処理方法です

{
  {1, 2, 3, 4, 5}
  {6, 7, 8, 9, 10}
  {11, 12, 13, 14, 15}
}
{
  {16, 17, 18, 19, 20}
  {21, 22, 23, 24, 25}
  {26, 27, 28, 29, 30}
}
{
  {31, 32, 33, 34, 35}
  {36, 37, 38, 39, 40}
  {41, 42, 43, 44, 45}
}
{
  {46, 47, 48, 49, 50}
  {51, 52, 53, 54, 55}
  {56, 57, 58, 59, 60}
}

このことから、各「パーティション」は、プログラムが追跡する単なる配列であることがはっきりとわかります。

構文

現在、配列はポインタと構文的に異なります .具体的には、これはコンパイラ/マシンがそれらを異なる方法で処理することを意味します。 これは簡単なことのように思えるかもしれませんが、これを見てください:

int a[3][3];

printf("%p %p", a, a[0]);

上記の例では、次のように同じメモリ アドレスを 2 回出力します。

0x7eb5a3b4 0x7eb5a3b4

ただし、ポインタに直接割り当てることができるのは 1 つだけです :

int *p1 = a[0]; // RIGHT !

int *p2 = a; // WRONG !

できない理由 a ポインタに割り当てられますが a[0] できますか?

これは単純に、多次元配列の結果であり、その理由を説明します:

a」のレベル '、楽しみにしている別の「次元」があることがわかります。 「a[0]」のレベル ' ただし、既に最上位の次元にいるため、プログラムに関する限り、通常の配列を見ているだけです。

あなたは尋ねているかもしれません:

ポインタの作成に関して、配列が多次元であることが問題になるのはなぜですか?

このように考えるのが最善です:

多次元配列からの「減衰」は単なるアドレスではなく、パーティション データを含むアドレスです。 (AKA は、基になるデータが他の配列で構成されていることを理解しています)。これは、最初の次元を超えて配列によって設定された境界で構成されます。

この「パーティション」ロジックは、指定しない限り、ポインター内に存在できません:

int a[4][5][95][8];

int (*p)[5][95][8];

p = a; // p = *a[0] // p = a+0

そうしないと、配列の並べ替えプロパティの意味が失われます。

*p を囲む括弧の使用にも注意してください :int (*p)[5][95][8] - これは、これらの境界を持つポインターの配列ではなく、これらの境界を持つポインターを作成していることを指定するためです:int *p[5][95][8]

結論

復習しましょう:

  • 使用されるコンテキストで他の目的がない場合、配列はアドレスに分解されます
  • 多次元配列は単なる配列の配列です - したがって、「劣化した」アドレスは「サブ次元を持っている」という負担を負います
  • 次元データはポインタに存在できません指定しない限り .

簡単に言うと、多次元配列はその内容を理解する能力を持つアドレスに崩壊します。


int *ptr= l_matrix[0];

のようにアクセスできます
*p
*(p+1)
*(p+2)

すべての 2 次元配列も 1-d として格納されます。