ポインターへのポインターはCでどのように機能しますか?いつ使用しますか?
ポインターへのポインターはCでどのように機能しますか?いつ使用しますか?
回答:
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 **
、したがって、ポインターへのポインターになります。char **
。f
はt **
、型の変数を変更する場合、型の引数を受け入れる必要がありますt *
。ポインターへのポインターは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次元配列を使用します。
私は、Git 2.0で7b1004bをコミットする、ポインタからポインタへのポインタのこの「実際の」コード例を気に入っています。
ライナスはかつて言った:
本当にコアな低レベルのコーディングをもっと多くの人に理解してもらいたいです。ロックレスの名前検索のような大きくて複雑なものではありませんが、ポインタからポインタへの適切な使用などです。
たとえば、「prev」エントリを追跡することにより、単一リンクリストエントリを削除する人が多すぎます、そしてエントリを削除するには、次のようなことをします
if (prev)
prev->next = entry->next;
else
list_head = entry->next;
そして、そのようなコードを見るときはいつでも、「この人はポインタを理解していません」とだけ行きます。悲しいことに、それは非常に一般的です。
ポインターを理解している人は、「エントリーポインターへのポインター」を使用し、list_headのアドレスで初期化します。そして、リストをトラバースするときに、条件を使用せずにエントリを削除できます。
*pp = entry->next
その単純化を適用すると、コメントを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行目)。
上記の特別なケースがあります-反復の開始時に前のエントリ(
prev
isNULL
)がないため、リストの最初のエントリを削除するには、ヘッド自体を変更する必要があります(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--
演算子を含む)。
大学のプログラミングコースの指針を説明するときに、それらについて学ぶ方法について2つのヒントが与えられました。1つ目は、BinkyのあるPointer Funを表示することでした。第二は、考えることだったHaddocksアイズルイス・キャロルさんからの通路鏡の国のアリス
「あなたは悲しいです」と騎士は心配そうな口調で言った:「あなたを慰めるためにあなたに歌を歌いましょう」
「それはとても長いのですか?」アリスはその日、たくさんの詩を聞いたので尋ねました。
「長いです」と騎士は言いました、「それはとても、とても美しいです。私が歌うのを聞いた人はみな、涙を流すか、それともそうではないでしょうか。」
「それとも他に?」とアリスは言いました。なぜなら、騎士は急に立ち止まったからです。
「そうでない場合は、そうではありません。この曲の名前は「ハドックス・アイズ」と呼ばれています。」
「ああ、それがその曲の名前だよね」とアリスは興味をそそろうと言った。
「いいえ、わかりません」と騎士は少し困惑して言った。「それがその名前の由来です。その名も「老人」。」
「それで私は 『それはその歌が呼ばれるものだ』と言わなければならないのですか?アリスは自分を正しました。
「いいえ、そうすべきではありません。それはまったく別のことです。この曲は「方法と手段」と呼ばれていますが、それはそれだけと呼ばれています。
「じゃあ、歌って何?」この時までに戸惑ったアリスは言った。
「私はそれに来ていました」と騎士は言いました。「この曲は本当に 'A-sitting On A Gate'で、曲は私自身の発明です。」
あなたはこれを読みたいかもしれません:ポインタからポインタへ
これがいくつかの基本的な疑問を明確にするのに役立つことを願っています。
ポインターへの参照が必要な場合。たとえば、呼び出された関数内の呼び出し関数のスコープで宣言されたポインター変数の値(ポイントされたアドレス)を変更したい場合。
引数として単一のポインターを渡すと、呼び出しスコープ内の元のポインターではなく、ポインターのローカルコピーが変更されます。ポインターへのポインターを使用して、後者を変更します。
ポインタへのポインタは、ハンドルとも呼ばれます。オブジェクトのメモリ内での移動や削除が可能な場合がよくあります。多くの場合、オブジェクトの使用をロックおよびロック解除して、アクセス時に移動しないようにします。
多くの場合、メモリが制限された環境、つまりPalm OSで使用されます。
以下の図とプログラムを検討して、この概念をよりよく理解してください。
図のように、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 = #
// 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
これは、ポインターのアドレス値へのポインターです。(それは私が知っているひどいことです)
基本的には、別のポインターのアドレスの値にポインターを渡すことができるため、次のように、サブ関数から別のポインターが指している場所を変更できます。
void changeptr(int** pp)
{
*pp=&someval;
}
ポインタへのポインタは、まあ、ポインタへのポインタです。
someType **の意味のある例は2次元配列です。1つの配列に他の配列へのポインターが入っているので、
dpointer [5] [6]
5番目の位置にある他の配列へのポインターを含む配列にアクセスし、ポインターを取得して(fpointerに名前を指定)、その配列を参照する配列の6番目の要素にアクセスします(つまり、fpointer [6])。
仕組み:別のポインタを格納できる変数です。
いつ使用するか:関数の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;
}
役立つ説明はたくさんありますが、簡単な説明はありませんでした。
基本的にポインタは変数のアドレスです。短い要約コード:
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など)を行う場合は、それらを使用する必要があります。
したがって、問題の明確化にも役立つことを願っています:)