web-dev-qa-db-ja.com

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

静的な2次元配列へのポインターが必要です。これはどのように行われますか?

static uint8_t l_matrix[10][20];

void test(){
   uint8_t **matrix_ptr = l_matrix; //wrong idea 
}

次のようなあらゆる種類のエラーが表示されます。

  • 警告:互換性のないポインタ型からの割り当て
  • 添え字付きの値は配列でもポインターでもありません
  • エラー:柔軟な配列メンバーの無効な使用
109
Dill

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

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では pointer/array world に注意してください。これについては多くの混乱があります。


編集

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

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

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

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

次に、20 uint8_tの配列型の要素の不完全な配列型へのポインターを作成します。ポインターは配列の配列を指しているため、次のようにアクセスする必要があります。

(*matrix_ptr)[0][1] = ...;

そして、それは不完全な配列へのポインターなので、cannotはショートカットとして行います

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

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


次の選択肢はまったく機能しません。1次元配列として表示する場合、配列の要素タイプはnotuint8_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まで動作することに気付くでしょうが、エイリアス分析やその他の積極的な最適化を行うと、一部のコンパイラーはそのコードを壊す可能性のある仮定を立てることができます。そうは言っても、失敗するコンパイラーに出会ったことはありません(しかし、実際のコードではその手法を使用していません)。CFAQにもその手法が含まれています(そのUB'nessに関する警告)、および配列タイプを変更できない場合、これはあなたを保存する最後のオプションです:)

129

これを完全に理解するには、must以下の概念を理解する必要があります:

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

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

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]のようにexactlyに格納されます-only違いは、プログラムが特定のサイズの個別のエンティティであるかのように管理するようになったことです(具体的には、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]can?

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

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

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

配列へのポインターの作成に関して配列が多次元である場合、なぜ重要なのですか?

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

多次元配列からの「崩壊」は単なるアドレスではなく、パーティションデータを持つアドレス(その基礎となるデータが他の配列)。これは、配列によって最初の次元を超えて設定された境界で構成されます。

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

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]



結論

確認してみましょう:

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

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

24
Super Cat

int *ptr= l_matrix[0];

あなたは次のようにアクセスできます

*p
*(p+1)
*(p+2)

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

7
Sagar

C99(clangおよびgccでサポート)には、多次元配列を参照によって関数に渡すためのわかりにくい構文があります。

int l_matrix[10][20];

void test(int matrix_ptr[static 10][20]) {
}

int main(void) {
    test(l_matrix);
}

プレーンポインターとは異なり、これは配列サイズに関するヒントであり、理論的にコンパイラーが小さすぎる配列を渡すことについて警告し、明らかな境界外アクセスを発見できるようにします。

残念ながら、sizeof()は修正されず、コンパイラーはまだその情報を使用していないようですので、好奇心が残ります。

5
Kornel

G'day、

宣言

static uint8_t l_matrix[10][20];

20個のunit8_tロケーション、つまり200個のuint8_tサイズのロケーションの10行のストレージを確保しており、各要素は20 x行+列を計算することで検出されます。

そうではない

uint8_t (*matrix_ptr)[20] = l_matrix;

必要なものを提供し、配列の最初の行の列ゼロ要素を指しますか?

編集:これについてもう少し考えると、配列名は、定義では、ポインターではありませんか?つまり、配列の名前は、最初の要素の場所の同義語、つまりl_matrix [0] [0]?です。

Edit2:他の人が述べたように、コメントスペースはこれ以上の議論には少し小さすぎます。とにかく:

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

問題のアレイにストレージの割り当てを提供しません。

前述のように、標準で定義されているように、ステートメントは次のとおりです。

static uint8_t l_matrix[10][20];

uint8_t型の200個の連続した場所を確保しました。

次の形式のステートメントを使用してl_matrixを参照します。

(*l_matrix + (20 * rowno) + colno)

行rownoにあるcolno'th要素の内容を提供します。

すべてのポインター操作は、ポイントされたオブジェクトのサイズを自動的に考慮します。 -K&Rセクション5.4、p.103

これは、手元のオブジェクトの格納にパディングまたはバイトアラインメントシフトが含まれる場合にも当てはまります。コンパイラーはこれらを自動的に調整します。 C ANSI標準の定義によります。

HTH

乾杯、

5
Rob Wells

配列を線形として宣言し、(row、col)から配列インデックスの計算を自分で行うことにより、コンパイラをいじるのを常に回避できます。

static uint8_t l_matrix[200];

void test(int row, int col, uint8_t val)

{

   uint8_t* matrix_ptr = l_matrix;
   matrix_ptr [col+y*row] = val; // to assign a value

}

これは、コンパイラーがとにかくしたことです。

4
gnosis

多次元配列を指すポインターを初期化する基本的な構文は次のとおりです。

type (*pointer)[ 1st dimension size ][2nd dimension size ][..]=&array_name

それを呼び出すための基本的な構文は

(*pointer_name)[ 1st index][2nd index][...]

ここに例があります

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
   char balance[5][100] = { {"Subham"},{"Messi"} };//The multidimentional array...

   char (*p)[5][100]=&balance;//Pointer initialization...

   printf("%s\n",(*p)[0]);//Calling...
   printf("%s\n",(*p)[1]);//Calling...

  return 0;
}

出力は次のとおりです。

Subham
Messi

それは...

1
Subham Debnath

次のようにできます:

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

最初の要素へのポインターが必要です。

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}
1
Ken Keenan

負のインデックスを使用する場合は、オフセットを追加することもできます。

uint8_t l_matrix[10][20];
uint8_t (*matrix_ptr)[20] = l_matrix+5;
matrix_ptr[-4][1]=7;

コンパイラーがエラーまたは警告を出す場合は、次を使用できます。

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