web-dev-qa-db-ja.com

Cの関数引数としてのポインター

たとえば、このコードを使用する場合:

_int num = 5;
int *ptr = #
_

次の2つの機能の違いは何ですか?

_void func(int **foo);
void func(int *foo); 
_

関数を呼び出す場所:

_func(&ptr); 
_

2つのうちの1つはパラメーターへのポインターへのポインターを使用し、2つ目のパラメーターはポインターのみを使用します。

func(&ptr)を渡すと、事実上ポインターを渡します。ポインターが別のポインターを指すのはどのような違いがありますか?

後者は非互換性の警告を出すと思いますが、あなたが何をしているのかを知っている限り、詳細は重要ではないようです。おそらく読みやすさと理解のために前者の方が良い選択肢(2つ星のポインター)のようですが、論理的な見地から見ると、違いは何ですか?

27
sherrellbc

合理的な経験則として、渡されたものを正確に変更することはできないので、呼び出し側はその変更を確認できます。回避策は、ポインターを渡すことです。

値渡し:void fcn(int foo)

値で渡す場合、値のコピーを取得します。関数で値を変更しても、変更に関係なく、呼び出し元には元の値が表示されます。

値へのポインタで渡す:void fcn(int* foo)

ポインターで渡すと、ポインターのコピーが得られます。ポインターのコピーは、元のポインターと同じメモリー位置を指します。このメモリの場所は、オリジナルが保存される場所です。これにより、ポイント先の値を変更できます。ただし、ポインタのコピーのみを受け取ったため、データへの実際のポインタを変更することはできません。

値へのポインターへのポインターを渡す:void fcn(int** foo)

値へのポインターへのポインターを渡すことで、上記の問題を回避できます。上記のように、値を変更して、呼び出し元のコードが使用しているのと同じメモリ位置であるため、呼び出し元に変更が表示されるようにすることができます。同じ理由で、ポインタを値に変更できます。これにより、関数内でメモリを割り当てて返すなどのことができます。 &arg2 = calloc(len);。ポインタをポインタに変更することはできません。これは、コピーを受け取るものだからです。

58
atk

違いは、プロセッサーがコードを処理する操作で簡単に言えます。値自体は両方のケースで単なるアドレスであり、それは本当です。ただし、アドレスが逆参照されると、プロセッサにとってもコンパイラにとっても重要になります。逆参照した後、それが何で処理されるかを知ることが重要です。

8
dhein

たとえば、このコードがある場合:

int num = 5;
int *ptr = #

次の2つの関数の違いは何ですか?:

void func(int **foo);
void func(int *foo);

最初のものはintへのポインターへのポインターが必要で、2番目のものはintを直接指すポインターが必要です。

関数を呼び出す場所:

func(&ptr);

ptrはintへのポインタであるため、&ptrint **と互換性のあるアドレスです。

int *をとる関数は、int **の場合とは少し異なります。会話の結果は完全に異なり、未定義の動作につながり、クラッシュを引き起こす可能性があります。

Func(&ptr)を渡すと、事実上ポインターを渡します。ポインターが別のポインターを指すのはどのような違いですか?

               +++++++++++++++++++
adr1 (ptr):    +  adr2           +
               +++++++++++++++++++

               +++++++++++++++++++
adr2 (num):    +  42             +
               +++++++++++++++++++

adr2には、int値42があります。

adr1には、ポインターのサイズを持つアドレスadr2があります。

&ptrはadr1、ptrを提供し、&num(adr2)の値を保持します。

adr1としてint *を使用すると、adr2は整数として誤って扱われ、(おそらく非常に大きな)数値になります。

adr2int **として使用すると、最初の逆参照が42になり、アドレスとして誤って解釈され、プログラムがクラッシュする可能性があります。

int *int **の違いは、単なる光学系ではありません。

後者は非互換性の警告を出すと思いますが、

...意味があります...

しかし、あなたが何をしているかを知っている限り、詳細は重要ではないようです。

あなたは?

おそらく読みやすさと理解のために前者の方が良い選択肢(2つ星のポインター)のようですが、論理的な見地から見ると、違いは何ですか?

関数がポインターで何をするかによって異なります。

7
glglgl

一般的に、この違いは、関数がポインターに割り当てられること、およびこの割り当てが関数に対してローカルであるべきではないことを示しています。たとえば(これらの例に注意してくださいfooの性質を調べるためであり、完全な関数ではなく、元の投稿のコードが実際に動作するコードであると想定される以上のものです):

_void func1 (int *foo) {
    foo = malloc (sizeof (int));
}

int a = 5;
func1 (&a);
_

と類似しています

_void func2 (int foo) {
    foo = 12;
}

int b = 5;
func2 (b);
_

fooはfunc2()で12に等しいかもしれませんが、func2()が戻るとき、bは5になります。func1()では、fooはaを指します。 new int。ただし、func1()が戻るとき、aaのままです。

aまたはbの値を変更する場合はどうなりますか? WRT b、通常のint:

_void func3 (int *foo) {
    *foo = 12;
}    

int b = 5;
func2 (&b);
_

動作します-intへのポインタが必要であることに注意してください。値を変更するにはinポインター(つまり、ポインターが指すintの値だけでなく、ポインターが指すintのアドレス):

_void func4 (int **foo) {
    *foo = malloc (sizeof (int));
}

int *a;
foo (&a);
_

'a'は、func4()のmallocによって返されるメモリを指します。アドレス_&a_は、aintへのポインターのアドレスです。 intポインターには、intのアドレスが含まれています。 func3()がintのアドレスを取得して新しいint値を入力できるように、func4()はintポインターのアドレスを取得してintのアドレスをこのアドレスに設定できます。

これが、さまざまな引数スタイルの使用方法です。

2つの主な実用的な違いがあります。

  1. ポインターをポインターに渡すと、関数は呼び出し元が見ることができる方法でそのポインターの内容を変更できます。古典的な例は、strtol()の2番目の引数です。 strtol()の呼び出しに続いて、そのポインターの内容は、long値を計算するために解析されなかったストリングの最初の文字を指す必要があります。ポインタをstrtol()に渡しただけであれば、それが行った変更はローカルになり、呼び出し元にその場所を知らせることは不可能になります。 strtol()は、そのポインターのアドレスを渡すことにより、呼び出し元が見ることができる方法でそれを変更できます。他の変数のアドレスを渡すようなものです。

  2. より基本的には、コンパイラは、逆参照するために、ポイントされている型を知る必要があります。たとえば、double *を間接参照する場合、コンパイラは、doubleが8バイトを消費する実装で、メモリ位置から始まる8バイトをdoubleの値として解釈します。ただし、32ビットの実装では、double **を間接参照すると、コンパイラはその場所から始まる4バイトを別のdoubleのアドレスとして解釈します。ポインターを間接参照する場合、ポイントされている型は、そのアドレスのデータを解釈する方法についてコンパイラーが持っている唯一の情報です。そのため、正確な型を知ることは重要です。すべてが単なるポインタなので、違いは何ですか?」

2
Paul Griffiths

これが尋ねられてからしばらく経ちましたが、これについての私の見解を以下に示します。私は今、Cを学ぼうとしていますが、ポインターはとてつもなく紛らわしいです...ですから、少なくとも私にとっては、ポインターのポインターを明確にするためにこの時間を取っています。ここに私がどう考えているかがあります。
ここ から例を取り上げました。

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

int allocstr(int len, char **retptr)
{
    char *p = malloc(len + 1);  /* +1 for \0 */
    if(p == NULL)
        return 0;
    *retptr = p;
    return 1;
}

int main()  
{
    char *string = "Hello, world!";
    char *copystr;
    if(allocstr(strlen(string), &copystr))
        strcpy(copystr, string);
    else    fprintf(stderr, "out of memory\n");
    return 0;
}

allocstrにダブルポインターが必要な理由が不思議でした。それがポインターである場合、それはあなたがそれを渡すことができることを意味し、戻り後に変更されます...
この例を行うと、問題なく動作します。ただし、allocstrを変更して、** pointer(およびcopystrメインの&copystrの代わりに) セグメンテーションエラー を取得します。どうして?コードにいくつかのprintfsを配置すると、strcpyの行まで正常に機能します。したがって、copystrにメモリを割り当てなかったと推測しています。繰り返しますが、なぜですか?
ポインタで渡すことの意味に戻りましょう。つまり、メモリの場所を渡し、そこに必要な値を直接書き込むことができます。値のメモリ位置にアクセスできるため、値を変更できます。
同様に、ポインターをポインターに渡すときは、ポインターの記憶場所、つまり記憶場所の記憶場所を渡します。そして今、(ポインタへのポインタ)ポインタだけを使用しているときに値を変更できるように、メモリの場所を変更できます。
コードが機能する理由は、メモリロケーションのアドレスを渡すためです。関数allocstrは、「Hello world!」を保持できるように、そのメモリ位置のサイズを変更しますそして、そのメモリ位置へのポインタを返します。
実際にはポインターを渡すのと同じですが、値の代わりにメモリの場所があります。

0
adi