Cポインターの配列/ポインターの配列の明確化


463

次の宣言の違いは何ですか?

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

より複雑な宣言を理解するための一般的なルールは何ですか?


54
Cでの複雑な宣言の読み取りに関するすばらしい記事を以下に示します。unixwiz.net
techtips

@jesper残念なことに、constおよびvolatile重要かつトリッキーともに修飾子は、その記事に欠けています。
ない-ユーザー

@ not-a-userこれらはこの質問には関係ありません。あなたのコメントは関係ありません。ご遠慮ください。
user64742

回答:


439
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

3つ目は1つ目と同じです。

一般的なルールは、演算子の優先順位です。関数ポインタが画面に入ると、さらに複雑になる可能性があります。


4
したがって、32ビットシステムの場合:int * arr [8]; / *ポインターごとに8x4バイトが割り当てられます/ int(* arr)[8]; / 4バイトが割り当てられ、ポインタのみ* /
ジョージ

10
いいえ。int * arr [8]:割り当てられた合計 8x4 バイト、各ポインターに4バイト。int(* arr)[8]は正しい、4バイトです。
Mehrdad Afshari、

2
私は自分が書いたものを読み直すべきだった。私は各ポインターに対して4を意味しました。助けてくれてありがとう!
ジョージ

4
最初のものと最後のものが同じである理由は、宣言子を括弧で囲むことが常に許可されているからです。P [N]は配列宣言子です。P(....)は関数宣言子であり、* Pはポインター宣言子です。したがって、以下のすべては括弧がない場合と同じです(関数の「()」の1つを除いて:int(((* p))); void((g(void))); int *(a [1]);(P()(*))ボイド。
litb -ヨハネスシャウブ

2
あなたの説明でよくやった。演算子の優先順位と結合性に関する詳細なリファレンスについては、Brian KernighanとDennis RitchieによるCプログラミング言語(ANSI C第2版)の53ページを参照してください。演算子は( ) [ ] 左から右に関連付けられ、各要素がintを指すサイズ8の配列として、および整数を保持するサイズ8の配列へのポインターとして*読み取らint* arr[8]れるよりも優先順位が高いint (*arr)[8]
Mushy

267

K&Rの提案に従って、cdeclプログラムを使用します。

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

それも逆の働きをします。

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )

@ankiiほとんどのLinuxディストリビューションにはパッケージが必要です。独自のバイナリを構築することもできます。
sigjuice

言及しないで申し訳ありません、macOSはこちらです。利用可能かどうかを確認します。それ以外の場合はWebサイトも問題ありません。^^この件についてお知らせいただきありがとうございます。NLNにご報告ください。
ankii

2
@ankii Homebrew(およびおそらくMacPorts?)からインストールできます。それらがあなたの好みに合わない場合は、cdecl.orgの右上にあるGithubリンクから自分でビルドするのは簡単です(私はmacOS Mojaveでビルドしました)。次に、cdeclバイナリをPATHにコピーします。$ PATH / binをお勧めします。これほど単純なものにrootを含める必要がないためです。
sigjuice

ああ、readmeのインストールに関する小さな段落を読んでいませんでした。依存関係を処理するためのいくつかのコマンドとフラグ。brewを使用してインストールされます。:)
ankii

1
これを最初に読んだとき、私は「私はこのレベルに落ちることは決してないだろう」と思いました。翌日、ダウンロードしました。
Ciro Santilli郝海东冠状病六四事件法轮功

126

正式名称はわかりませんが、Right-Left Thingy(TM)と呼んでいます。

変数から始めて、右、左、右...と続きます。

int* arr1[8];

arr1 整数への8つのポインタの配列です。

int (*arr2)[8];

arr2 8つの整数の配列へのポインタ(括弧は右から左へのブロック)です。

int *(arr3[8]);

arr3 整数への8つのポインタの配列です。

これは、複雑な宣言の助けになるはずです。


19
「螺旋の法則」という名前で呼ばれていると聞きまし
2013年

6
@InkBlend:スパイラルルールは、左右のルールとは異なります。前者は失敗したような場合にはint *a[][10]、後者が成功している間。
legends2k 2013年

1
@dogeenその用語はBjarne Stroustrupと関係があると思いました:)
Anirudh Ramanathan

1
InkBlendとlegends2kが言ったように、これはスパイラルルールであり、より複雑であり、すべてのケースで機能するわけではないため、使用する理由はありません。
コトロモイ2014

( ) [ ]左から右への関連性と右から左への左を忘れないでください* &
Mushy

28
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 

3番目のものはそうではありません:aはサイズ8の整数配列へのポインタの配列ですか?つまり、各整数配列のサイズは8になりますよね?
ルシルポール

2
@ルシル:いいえ、最後の添え字([5])は内側の次元を表します。これは、これ(*a[8])が最初の次元であり、配列の外部表現であることを意味します。何内の各要素a を指すサイズ5の異なる整数配列である
zeboidlund

三番目をありがとう。配列へのポインタの配列を書き込む方法を探しています。
Deqing 2013

15

最後の2つの答えは、Cの黄金律から差し引くこともできます。

使用後の宣言。

int (*arr2)[8];

逆参照するとどうなりますarr2か?8つの整数の配列を取得します。

int *(arr3[8]);

から要素を取得するとどうなりますarr3か?整数へのポインタを取得します。

これは、関数へのポインターを処理するときにも役立ちます。sigjuiceの例をとると:

float *(*x)(void )

逆参照するとどうなりますxか?引数なしで呼び出すことができる関数を取得します。あなたがそれを呼ぶとどうなりますか?へのポインタを返しますfloat

ただし、演​​算子の優先順位は常に注意が必要です。ただし、かっこを使用すると、宣言の後に使用されるため、混乱を招く可能性もあります。少なくとも、私には、直感的にarr2は、intへの8つのポインタの配列のように見えますが、実際には逆です。慣れるだけです。あなたが私に尋ねるならば、これらの宣言に常にコメントを追加するのに十分な理由:)

編集:例

ちなみに、私は次の状況に遭遇しました。静的行列があり、ポインター演算を使用して行ポインターが範囲外かどうかを確認する関数です。例:

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

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

出力:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

borderの値は決して変更されないため、コンパイラーはそれを最適化することができます。これは、最初に使用する可能性のあるものとは異なります。const int (*border)[3]:変数が存在する限り値を変更しない3つの整数の配列へのポインターとしてボーダーを宣言します。ただし、そのポインタは、他のそのような配列をいつでもポイントできます。代わりに、この種の引数の動作が必要です(この関数はこれらの整数を変更しないため)。使用後の宣言。

(ps:このサンプルを自由に改善してください!)



3

経験則として、(のような右単項演算子[]()などは、)左のものよりも優先してください。したがって、int *(*ptr)()[];intへのポインタの配列を返す関数を指すポインタになります(かっこから抜けたらできるだけ早く正しい演算子を取得してください)


それは本当ですが、それも違法です。配列を返す関数を持つことはできません。私は試してこれを得ました:error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];GCC 8の下で$ gcc -std=c11 -pedantic-errors test.c
カカウエテフリト

1
コンパイラがそのエラーを表示するのは、優先ルールの正しい解釈が示すように、関数が配列を返すものとして解釈しているためです。宣言としては違法ですが、法的な宣言でint *(*ptr)();p()[3](または(*p)()[3])のような式を後で使用できます。
Luis Colorado

わかりました、私はそれを理解している場合、配列の最初の要素(配列自体ではない)へのポインターを返す関数を作成し、後でその関数を配列を返すかのように使用することについて話していますか?面白いアイデア。私はそれを試してみます。int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }そして、このようにそれを呼び出す:foo(arr)[4];含めるべきarr[2][4]、右?
カカウエテフリト

正しい...でもあなたは正しい、そして宣​​言は違法だった。:)
Luis Colorado

2

簡単なルールが使えると思います。

example int * (*ptr)()[];
start from ptr 

" ptrは、"右方向に行く..its ")"へのポインターです。今、左方向に移動します。 「配列へ」右「整数の左へ」


私はそれを少し改善します:「ptrは参照する名前です」右に行く...それ)は今すぐ左に行く...それは*「へのポインタ」です右に行く...それは)、今左に行く...それは(出てくる、右に行く()...右に行く「引数を取らない関数に」そう[]「との配列を返す」右に行く;の端を、その左に行く... *行く左...「へのポインタ」int「整数」
カカウエテフリト


2

ここに私がそれを解釈する方法があります:

int *something[n];

優先順位に関する注意:配列添字演算子([])は、逆参照演算子(*)よりも優先されます。

したがって、ここでは[]before を適用*し、ステートメントを次と同等にします。

int *(something[i]);

宣言がどのように意味をなすかに注意してください。int num平均numintint *ptrまたはint (*ptr)平均、(値at ptr)はでありint、これはへptrのポインターになりintます。

これは、((何かのi番目のインデックスの値)の値)は整数として読み取ることができます。したがって、(何かのi番目のインデックスの値)は(整数ポインター)であり、これにより、何かが整数ポインターの配列になります。

二つ目は

int (*something)[n];

このステートメントを理解するには、この事実に精通している必要があります。

配列のポインタ表現に関する注意:somethingElse[i]と同等*(somethingElse + i)

したがって、に置き換えるsomethingElse(*something)*(*something + i)宣言に従って整数であるが得られます。したがって、(*something)配列を指定すると、(配列へのポインター)と同等のものになります


0

2番目の宣言は多くの人を混乱させると思います。これを理解する簡単な方法を次に示します。

整数の配列、すなわちint B[8]

また、Bを指す変数Aがあるとします。ここで、Aの値はB、つまり(*A) == Bです。したがって、Aは整数の配列を指します。あなたの質問では、arrはAに似ています。

同様にint* (*C) [8]、Cは整数へのポインターの配列へのポインターです。


0
int *arr1[5]

この宣言でarr1は、整数への5つのポインタの配列です。理由:角かっこは*(逆参照演算子)よりも優先されます。このタイプでは、行数は固定されていますが(ここでは5)、列数は可変です。

int (*arr2)[5]

この宣言でarr2は、は5要素の整数配列へのポインタです。理由:ここで、()括弧は[]よりも優先されます。このタイプでは、行数は可変ですが、列数は固定です(ここでは5)。


-7

整数へのポインタでは、ポインタがインクリメントされると、次の整数に進みます。

ポインタの配列では、ポインタがインクリメントされると次の配列にジャンプします


" ポインタの配列では、ポインタがインクリメントされると次の配列にジャンプします "これは明らかに間違っています。
alk 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.