ポインターへのポインターはCでどのように機能しますか?


171

ポインターへのポインターはCでどのように機能しますか?いつ使用しますか?


43
宿題ではありません....知りたかっただけです。Cコードを読んだとき、よくわかります。

1
ポインターへのポインターは何かの特別なケースではないので、私はvoid **についてあなたが理解していないことを理解していません。
アカッパ2009年

2D配列の最も良い例は、コマンドラインargs "prog arg1 arg2"がchar ** argvに格納されていることです。そして、呼び出し元がメモリを割り当てたくない場合(呼び出された関数がメモリを割り当てる)
resultsway

1
Git 2.0での「ポインター
ツー

回答:


359

8ビットアドレス(したがって256バイトのメモリのみ)を備えた8ビットコンピュータを想定します。これはそのメモリの一部です(上部の数字はアドレスです):

  54   55   56   57   58   59   60   61   62   63   64   65   66   67   68   69
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|    | 58 |    |    | 63 |    | 55 |    |    | h  | e  | l  | l  | o  | \0 |    |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

ここでわかるのは、アドレス63で文字列「hello」が始まるということです。したがって、この場合、これがメモリ内で「hello」の唯一の発生である場合、

const char *c = "hello";

...はc(読み取り専用)文字列 "hello"へのポインタとして定義されているため、値63が含まれています。cそれ自体がどこかに格納されている必要があります。上記の例では、位置58にあります。もちろん、文字だけを指すことはできませんだけでなく、他のポインタにも。例えば:

const char **cp = &c;

これはをcp指します。cつまり、アドレスc(58)が含まれています。さらに進むことができます。考慮してください:

const char ***cpp = &cp;

cppのアドレスを格納しますcp。したがって、値は55(上記の例に基づく)であり、推測したとおりです。それ自体がアドレス60に格納されています。


ポインタへのポインタを使用する理由について

  • 配列の名前は通常、最初の要素のアドレスを生成します。したがって、配列に型の要素が含まれている場合、配列tへの参照には型がありt *ます。次に、タイプの配列の配列を考えますt。当然、この2D配列への参照はタイプ(t *)*= を持ちt **、したがって、ポインターへのポインターになります。
  • 文字列の配列は1次元に聞こえますが、文字列は文字配列であるため、実際には2次元です。したがって:char **
  • 関数ft **、型の変数を変更する場合、型の引数を受け入れる必要がありますt *
  • ここにリストするには数が多すぎる他の多くの理由。

7
はい良いexample..i ..彼らはより多くのimportant..nowでいつ、どのようにそれらを使用するare..butか理解

2
ステファンは、基本的には、カーニハン&リッチーの「Cプログラミング言語」の図を再現する上手な仕事をしました。Cをプログラミングしていて、この本を持っていなくて、紙のドキュメントが得意な場合は、それを入手することを強くお勧めします。その例では非常に明確になる傾向があります。
J. Polfer、2009年

4
char * c = "hello"はconst char * c = "hello"である必要があります。また、「配列は最初の要素のアドレスとして格納される」と言っても、せいぜい誤解を招くだけです。配列は配列として保存されます。多くの場合、その名前は最初の要素へのポインタを生成しますが、常にそうであるとは限りません。ポインターへのポインターについては、関数がパラメーターとして渡されたポインターを変更する必要がある場合(代わりにポインターをポインターに渡す場合)に役立つと簡単に言えます。
BastienLéonard09年

4
この答えを誤解しない限り、それは間違っているように見えます。cは58に格納され、63をポイントします。cpは55に格納され、58をポイントします。cppは図に表されていません。
タナトス

1
いいね。マイナーな問題よりも、私が言うことを止めていたすべてのものでした:素晴らしいポスト 説明自体は素晴らしかった。賛成票に変更します。(おそらく、stackoverflowはポインタを確認する必要がありますか?)
タナトス

46

ポインターへのポインターはCでどのように機能しますか?

まず、ポインターは他の変数と同様に変数ですが、変数のアドレスを保持します。

ポインターへのポインターは、他の変数と同様に変数ですが、変数のアドレスを保持します。その変数はたまたまポインタです。

いつ使用しますか?

ヒープ上の一部のメモリへのポインタを返す必要があるが、戻り値を使用しない場合に使用できます。

例:

int getValueOf5(int *p)
{
  *p = 5;
  return 1;//success
}

int get1024HeapMemory(int **p)
{
  *p = malloc(1024);
  if(*p == 0)
    return -1;//error
  else 
    return 0;//success
}

そしてあなたはそれをこのように呼びます:

int x;
getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in
//At this point x holds 5

int *p;    
get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in
//At this point p holds a memory address where 1024 bytes of memory is allocated on the heap

すべてのCプログラムのmain()引数にargvのポインターへのポインターがあり、各要素がコマンドラインオプションである文字の配列を保持するなど、他の用途もあります。ただし、ポインタのポインタを使用して2次元配列を指す場合は、代わりに2次元配列へのポインタを使用することをお勧めします。

なぜ危険なのですか?

void test()
{
  double **a;
  int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*)

  double matrix[ROWS][COLUMNS];
  int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double)
}

以下は、正しく行われた2次元配列へのポインターの例です。

int (*myPointerTo2DimArray)[ROWS][COLUMNS]

2次元配列へのポインタを使用することはできませんが、ROWSとCOLUMNSで可変数の要素をサポートする必要がある場合です。ただし、事前に知っている場合は、2次元配列を使用します。


32

私は、Git 2.0で7b1004bをコミットする、ポインタからポインタへのポインタのこの「実際の」コード例を気に入っています。

ライナスはかつて言った:

本当にコアな低レベルのコーディングをもっと多くの人に理解してもらいたいです。ロックレスの名前検索のような大きくて複雑なものではありませんが、ポインタからポインタへの適切な使用などです。
たとえば、「prev」エントリを追跡することにより、単一リンクリストエントリを削除する人が多すぎます、そしてエントリを削除するには、次のようなことをします

if (prev)
  prev->next = entry->next;
else
  list_head = entry->next;

そして、そのようなコードを見るときはいつでも、「この人はポインタを理解していません」とだけ行きます。悲しいことに、それは非常に一般的です。

ポインターを理解している人は、「エントリーポインターへのポインター使用し、list_headのアドレスで初期化します。そして、リストをトラバースするときに、条件を使用せずにエントリを削除できます。

*pp =  entry->next

http://i.stack.imgur.com/bpfxT.gif

その単純化を適用すると、コメントを2行追加しても、この関数から7行を失うことができます。

-   struct combine_diff_path *p, *pprev, *ptmp;
+   struct combine_diff_path *p, **tail = &curr;

Chris は、Philip Buuckによる2016年のビデオ「Linus Torvaldsのダブルポインター問題」へのコメントで指摘しています


クマーは指摘するコメントで「ブログ記事の概要ポインタのライナス、」Grisha Trubetskoyは説明しています。

次のように定義されたリンクリストがあるとします。

typedef struct list_entry {
    int val;
    struct list_entry *next;
} list_entry;

最初から最後まで繰り返し、値がto_removeの値と等しい特定の要素を削除する必要があります。
これを行うためのより明白な方法は次のとおりです。

list_entry *entry = head; /* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;

while (entry) { /* line 4 */
    if (entry->val == to_remove)     /* this is the one to remove ; line 5 */
        if (prev)
           prev->next = entry->next; /* remove the entry ; line 7 */
        else
            head = entry->next;      /* special case - first entry ; line 9 */

    /* move on to the next entry */
    prev = entry;
    entry = entry->next;
}

上記で行っていることは次のとおりです。

  • エントリがNULLになるまでリストを反復します。これは、リストの最後に到達したことを意味します(4行目)。
  • 削除したいエントリ(5行目)に遭遇すると、
    • 現在の次のポインタの値を前のポインタに割り当て、
    • したがって、現在の要素を削除します(7行目)。

上記の特別なケースがあります-反復の開始時に前のエントリ(previs NULL)がないため、リストの最初のエントリを削除するには、ヘッド自体を変更する必要があります(9行目)。

Linusが言っていたことは、前の要素を単なるポインタではなくポインタへのポインタにすることで、上記のコードを簡略化できるということです。
コードは次のようになります。

list_entry **pp = &head; /* pointer to a pointer */
list_entry *entry = head;

while (entry) {
    if (entry->val == to_remove)
        *pp = entry->next;

    pp = &entry->next;
    entry = entry->next;
}

上記のコードは前のバリアントと非常に似ていますが、最初でppはないため、リストの最初の要素の特殊なケースを監視する必要がないことに注意してNULLください。シンプルで賢い。

また、そのスレッドの誰かが、これがより良い理由*pp = entry->nextはアトミックだからであるとコメントしました。間違いなくアトミックではありません
上記の式には、2つの間接参照演算子(*および->)と1つの代入が含まれていますが、これら3つはいずれもアトミックではありません。
これはよくある誤解ですが、悲しいかな、Cではほとんど何もアトミックであると想定すべきではありません++and --演算子を含む)。


4
これは理解を深めるのに役立ちます-grisha.org/blog/2013/04/02/linus-on-understanding-pointers
kumar

@kumar良い参照。私はより多くの可視性のために答えにそれを含めました。
VonC、2014年

このビデオは私があなたの例を理解するのに不可欠でした。特に、記憶図を描き、プログラムの進行状況をたどるまで、私は混乱(そして好戦的)と感じていました。とは言っても、それはまだ私には幾分不思議に思えます。
Chris

@クリス素晴らしいビデオ、それを言及してくれてありがとう!見やすくするために、回答にコメントを含めました。
VonC 2017

14

大学のプログラミングコースの指針を説明するときに、それらについて学ぶ方法について2つのヒントが与えられました。1つ目は、BinkyのあるPointer Funを表示することでした。第二は、考えることだったHaddocksアイズルイス・キャロルさんからの通路鏡の国のアリス

「あなたは悲しいです」と騎士は心配そうな口調で言った:「あなたを慰めるためにあなたに歌を歌いましょう」

「それはとても長いのですか?」アリスはその日、たくさんの詩を聞いたので尋ねました。

「長いです」と騎士は言いました、「それはとても、とても美しいです。私が歌うのを聞いた人はみな、涙を流すか、それともそうではないでしょうか。」

「それとも他に?」とアリスは言いました。なぜなら、騎士は急に立ち止まったからです。

「そうでない場合は、そうではありません。この曲の名前は「ハドックス・アイズ」と呼ばれています。」

「ああ、それがその曲の名前だよね」とアリスは興味をそそろうと言った。

「いいえ、わかりません」と騎士は少し困惑して言った。「それがその名前の由来です。その名も「老人」。」

「それで私は 『それはその歌が呼ばれるものだ』と言わなければならないのですか?アリスは自分を正しました。

「いいえ、そうすべきではありません。それはまったく別のことです。この曲は「方法と手段」と呼ばれていますが、それはそれだけと呼ばれています。

「じゃあ、歌って何?」この時までに戸惑ったアリスは言った。

「私はそれに来ていました」と騎士は言いました。「この曲は本当に 'A-sitting On A Gate'で、曲は私自身の発明です。」


1
私はその箇所を数回読む必要がありました...考えさせるための+1!
ルーベンシュタインズ

これが、ルイス・キャロルが普通の作家ではない理由です。
メタローズ2017年

1
だから…こうなるの?名前-> 'The Aged Aged Man'->呼び出された-> 'Haddock's Eyes'->歌-> 'A-sitting On A Gate'
tisaconundrum


7

ポインターへの参照が必要な場合。たとえば、呼び出された関数内の呼び出し関数のスコープで宣言されたポインター変数の値(ポイントされたアドレス)を変更したい場合。

引数として単一のポインターを渡すと、呼び出しスコープ内の元のポインターではなく、ポインターのローカルコピーが変更されます。ポインターへのポインターを使用して、後者を変更します。


「なぜ」の部分についてはよく説明されています
Rana Deep

7

ポインタへのポインタは、ハンドルとも呼ばれます。オブジェクトのメモリ内での移動や削除が可能な場合がよくあります。多くの場合、オブジェクトの使用をロックおよびロック解除して、アクセス時に移動しないようにします。

多くの場合、メモリが制限された環境、つまりPalm OSで使用されます。

computer.howstuffworks.comリンク>>

www.flippinbits.comリンク>>


7

以下の図とプログラムを検討して、この概念をよりよく理解してください

ダブルポインター図

図のように、ptr1は変数numのアドレスを持つ単一のポインターです。

ptr1 = #

同様に、ptr2は、ポインターptr1のアドレスを持つポインター(ダブルポインター)へのポインターです

ptr2 = &ptr1;

別のポインターを指すポインターは、ダブルポインターと呼ばれます。この例では、ptr2は二重ポインターです。

上図の値:

Address of variable num has : 1000
Address of Pointer ptr1 is: 2000
Address of Pointer ptr2 is: 3000

例:

#include <stdio.h>

int main ()
{
   int  num = 10;
   int  *ptr1;
   int  **ptr2;

   // Take the address of var 
   ptr1 = &num;

   // Take the address of ptr1 using address of operator &
   ptr2 = &ptr1;

   // Print the value
   printf("Value of num = %d\n", num );
   printf("Value available at *ptr1 = %d\n", *ptr1 );
   printf("Value available at **ptr2 = %d\n", **ptr2);
}

出力:

Value of num = 10
Value available at *ptr1 = 10
Value available at **ptr2 = 10

5

これは、ポインターのアドレス値へのポインターです。(それは私が知っているひどいことです)

基本的には、別のポインターのアドレスの値にポインターを渡すことができるため、次のように、サブ関数から別のポインターが指している場所を変更できます。

void changeptr(int** pp)
{
  *pp=&someval;
}

申し訳ありませんが、それはかなり悪かったことを知っています。これを読んでみてください、これ、codeproject.com
Luke Schafer

5

何かのアドレスを含む変数があります。それはポインタです。

次に、最初の変数のアドレスを含む別の変数があります。それはポインタへのポインタです。


3

ポインタへのポインタは、まあ、ポインタへのポインタです。

someType **の意味のある例は2次元配列です。1つの配列に他の配列へのポインターが入っているので、

dpointer [5] [6]

5番目の位置にある他の配列へのポインターを含む配列にアクセスし、ポインターを取得して(fpointerに名前を指定)、その配列を参照する配列の6番目の要素にアクセスします(つまり、fpointer [6])。


2
ポインタへのポインタは、ランク2の配列と混同しないようにしてください。たとえば、x [5] [6]と書いたint x [10] [10]では、配列の値にアクセスします。
ピートカーカム

これはvoid **が適切な例にすぎません。ポインタへのポインタは、ポインタを指すポインタにすぎません。
アカッパ2009年

1

仕組み:別のポインタを格納できる変数です。

いつ使用するか:関数の1つは、関数が配列を作成して呼び出し元に返す場合です。

//returns the array of roll nos {11, 12} through paramater
// return value is total number of  students
int fun( int **i )
{
    int *j;
    *i = (int*)malloc ( 2*sizeof(int) );
    **i = 11;  // e.g., newly allocated memory 0x2000 store 11
    j = *i;
    j++;
    *j = 12; ;  // e.g., newly allocated memory 0x2004 store 12

    return 2;
}

int main()
{
    int *i;
    int n = fun( &i ); // hey I don't know how many students are in your class please send all of their roll numbers.
    for ( int j=0; j<n; j++ )
        printf( "roll no = %d \n", i[j] );

    return 0;
}


0

役立つ説明はたくさんありますが、簡単な説明はありませんでした。

基本的にポインタは変数のアドレスです。短い要約コード:

     int a, *p_a;//declaration of normal variable and int pointer variable
     a = 56;     //simply assign value
     p_a = &a;   //save address of "a" to pointer variable
     *p_a = 15;  //override the value of the variable

//print 0xfoo and 15 
//- first is address, 2nd is value stored at this address (that is called dereference)
     printf("pointer p_a is having value %d and targeting at variable value %d", p_a, *p_a); 

また、参考情報は、トピック「参照と逆参照の意味」にあります

そして、いつポインタが役立つかはわかりませんが、一般的に、手動/動的メモリ割り当て(malloc、callocなど)を行う場合は、それらを使用する必要があります

したがって、問題の明確化にも役立つことを願っています:)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.