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


119

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

static uint8_t l_matrix[10][20];

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

次のようなさまざまなエラーが発生します。

  • 警告:互換性のないポインタ型からの割り当て
  • 下付きの値は配列でもポインタでもありません
  • エラー:フレキシブル配列メンバーの無効な使用

stackoverflow.com/questions/423823/を読んでください…役立つかもしれません
Johannes Schaub-litb

1
@ JohannesSchaub-litbそれはもう存在しません。(再度表示するにはどうすればよいですか?低
応答

@muntoo:ここでのコピーがあります:gist.github.com/sharth/ede13c0502d5dd8d45bd
ビル・リンチ

回答:


139

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

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次元配列として表示すると、それがではない 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が、エイリアス分析やその他の積極的な最適化を行うと、一部のコンパイラはそのコードを壊す可能性があると仮定する可能性があります。とは言っても、失敗するコンパイラーに遭遇したことはありません(しかし、実際のコードではその手法を使用していません)。CFAQにもその手法が含まれています(そのUBについての警告付き) )、そして配列タイプを変更できない場合、これはあなたを救う最後のオプションです:)


+1-intに関する素晴らしい情報(*)[] [20]の内訳-C ++ではそれを実行できない
ファイサルヴァリ

@litb、申し訳ありませんが、ソリューションはアレイにストレージの割り当てを提供しないため、これは誤りです。
Rob Wells、

2
@ロブ、よくわからない。これらすべての場合のストレージは、配列l_matix自体によって提供されます。それらへのポインタは、それらが宣言されている場所とスタックから、ストレージを取得します(スタック、静的データセグメントなど)。
ヨハネスシャウブ-litb 2009年

l_matrixの「&」アドレスが必要なのはなぜですか?
エレクトロ

1
@Sohaib-いいえ、1つのポインタしか作成しません。uint8_t *d[20]uint8_tへの3つのポインターの配列を作成すると混同している可能性がありますが、この場合は機能しません。
パロ

27

完全にこのことを理解し、あなたがしなければなりませんは、次の概念ます。

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

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

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]vsプレーンを使用することの利点int b[60]は、それらが「パーティション化」され(必要に応じて、「チャンク」での作業がより簡単になる)、プログラムが境界チェックを実行できるようになることです。

実際にint a[4][3][5]は、メモリとまったく同じようint b[60]に格納されます。唯一の違いは、プログラムが特定のサイズの個別のエンティティ(具体的には、5つのグループの3つのグループ)であるかのようにプログラムを管理することです。

覚えておいてください: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]ただし、' ' のレベルでは、すでに最上位の次元にいるので、プログラムに関する限り、通常の配列を調べているだけです。

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

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

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

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

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

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

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

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

そうしないと、配列のソートプロパティの意味が失われます。

また、周りの括弧の使用を注意してください*pint (*p)[5][95][8]-我々はこれらの限界は、これらの境界を持つポインタのない配列とポインタを作っていることを指定すること:int *p[5][95][8]



結論

確認してみましょう:

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

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


1
答えの最初の部分は素晴らしいですが、次の部分はそうではありません。これは正しくありません:int *p1 = &(a[0]); // RIGHT !、実際には次と同じですint *p1 = a;
2501 '25

@ 2501そのエラーを見つけていただきありがとうございます。修正しました。この「ルール」を定義する例がなぜそれを無視したのかは、はっきりとは言えません。言い換えると、2つのエンティティがポインタとして解釈され、同じ値を生成できるからといって、同じ意味を持つわけではありません。
スーパーキャット

7

int *ptr= l_matrix[0];

次のようにアクセスできます

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

結局、2次元配列も1-dとして格納されます。


5

G'day、

宣言

static uint8_t l_matrix[10][20];

unit8_tの20の場所、つまり200 uint8_tサイズの場所の10行のストレージを確保しました。各要素は、20 x行+列を計算することで見つかります。

そうではない

uint8_t (*matrix_ptr)[20] = l_matrix;

必要なものを与えて、配列の最初の行の列0の要素をポイントしますか?

編集:これについてもう少し考えてみると、配列名は定義上、ポインタではありませんか?つまり、配列の名前は最初の要素の場所の同義語です。すなわち、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番目の要素の内容を提供します。

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

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

HTH

乾杯、


1
uint8_t(* matrix_ptr)[] [20] <<最初の角かっこは除外する必要があります。正しいのはuint8_t(* matrix_ptr)[20]
アコンカグア

5

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

int l_matrix[10][20];

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

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

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

悲しいことに、それは修正されずsizeof()、コンパイラーはまだその情報を使用していないようです。そのため、それは好奇心のままです。


1
この答えは誤解を招くものです。それは引数を固定サイズの配列にするのではなく、それはまだポインタです。static 10、少なくとも 10要素が存在することを保証するものです。これも、サイズが固定されていないことを意味します。
ブラス

1
@bluss質問はポインターに関するものだったので、ポインターでの回答(参照による注記)がどのように誤解を招くのかわかりません。これらの境界を超える要素へのアクセスは定義されていないため、配列は関数の観点から固定サイズです。
Kornel 2016年

10を超えるアクセスは未定義ではないと思います。それを示すものは何も表示されません。
ブラス

この回答は、キーワードstaticがないと、配列が参照渡しされないことを示唆しているようですが、これは正しくありません。配列はとにかく参照渡しされます。元の質問は別のユースケースについて尋ねました-同じ関数/名前空間内の補助ポインターを使用して2D配列の要素にアクセスします。
パロ

4

配列を線形として宣言し、(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

}

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


1
これはとにかくCコンパイラが行うことです。Cには実際には「配列」の実際の概念はありません。[]表記は、ポインタ演算の構文糖衣です
Ken Keenan

7
このソリューションには、正しい方法を見つけられないという欠点があります。
クレイグマックイーン

2

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

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() {
   // The multidimentional array...
   char balance[5][100] = {
       "Subham",
       "Messi"
   };

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

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

  return 0;
}

出力は次のとおりです。

Subham
Messi

出来た...


1

あなたはこのようにそれを行うことができます:

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

1
これは10 * 20バイトのRAMを占有しませんか?(マイクロコントローラーを使用している場合)
Dill

これは、ボックス内で4バイトまたはポインタが大きいサイズを占めます。ただし、これがある場合は、matrix_ptr [0] [x] [y]または(* matrix_ptr)[x] [y]を使用してインデックス付けする必要があります。P:それは直接的だと「二次元配列へのポインタ」の単語ごとの単語の解釈
litb -ヨハネス・シャウブ

litbに感謝します。それにアクセスする方法を説明するのを忘れていました。あなたはあなたの答えで素晴らしい仕事をしたので、私の答えを編集する意味はありません:)
Nick Dandoulakis

それで、これは10 * 20バイトのRAMを占有するのでしょうか?
Danijel、2018年

@Danijel、それは2次元配列へのポインターであるため、4バイトまたはボックス内のポインターのサイズ(16ビット、32ビット、64ビットなど)を占めるだけです
Nick Dandoulakis

1

最初の要素へのポインタが必要なので、

static uint8_t l_matrix[10][20];

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

0

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

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;

こんにちは。この質問にはがタグ付けされているcため、回答は同じ言語である必要があります。タグを確認してください。
2501
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.