web-dev-qa-db-ja.com

どのようにしてメモリを割り当て、それを(ポインタパラメータを介して)呼び出し元の関数に返すことができますか?

次のようなコードがいくつかの異なる関数に含まれています。

_void someFunction (int *data) {
  data = (int *) malloc (sizeof (data));
}

void useData (int *data) {
  printf ("%p", data);
}

int main () {
  int *data = NULL;

  someFunction (data);

  useData (data);

  return 0;
}
_

someFunction ()およびuseData ()は、個別のモジュール(* .cファイル)で定義されています。

問題は、mallocが正常に動作し、割り当てられたメモリがsomeFunctionで使用可能である一方で、関数が返されると同じメモリを使用できないことです。

プログラムの実行例は、さまざまなメモリアドレスを示す出力とともに here で確認できます。

誰かが私にここで何が間違っているのか、そしてこのコードを機能させるにはどうしたらいいですか


編集:これを行うにはダブルポインタを使用する必要があるようです-実際に使用する必要があるときにどうすれば同じことを実行できますかダブルポインタ?したがって、たとえばデータは

_int **data = NULL; //used for 2D array
_

次に、関数呼び出しでトリプルポインターを使用する必要がありますか?

31
a_m0d

ポインターツーポインターを使用する場合:

_void someFunction (int **data) {
  *data = malloc (sizeof (int));
}

void useData (int *data) {
  printf ("%p", data);
}

int main () {
  int *data = NULL;

  someFunction (&data);

  useData (data);

  return 0;
}
_

どうして?さて、メイン関数のdataを変更したいとします。 Cでは、パラメーターとして渡されたものを変更する場合(およびその変更を呼び出し元のバージョンに表示させる場合)、変更するものへのポインターを渡す必要があります。この場合、「変更したいもの」はポインターです。そのポインターを変更できるようにするには、ポインターツーポインターを使用する必要があります...

主な問題に加えて、コードに別のバグがあったことに注意してください:sizeof(data)は、ポインターを格納するために必要なバイト数を示します(32ビットOSでは4バイト、64ビットでは8バイト) -bit OS)、ただし、ポインタが指すものを保存するために必要なバイト数が本当に必要な場合int、つまりほとんどのOSでは4バイト) 。通常はsizeof(int *)>=sizeof(int)であるため、問題が発生することはおそらくありませんが、注意する必要があります。上記のコードでこれを修正しました。

ポインターからポインターへのいくつかの有用な質問はここにあります:

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

複数レベルのポインター逆参照に使用しますか?

58
Martin B

特にJavaをC/C++に移動した場合の一般的な落とし穴

ポインタを渡すときは、値渡しです。つまり、ポインタの値がコピーされます。ポインタが指すデータに変更を加えるのに適していますが、ポインタ自体への変更はコピーであるため、ローカルです。

トリックは、ポインターを変更したいので、参照でポインターを渡すことです。つまり、mallocなどです。

**ポインタ->初心者Cプログラマを怖がらせます;)

8
Sid Sarasvati

ポインターを変更する場合は、ポインターをポインターに渡す必要があります。

すなわち。 :

void someFunction (int **data) {
  *data = malloc (sizeof (int)*ARRAY_SIZE);
}

編集:ARRAY_SIZEを追加しました。ある時点で、割り当てたい整数の数を知る必要があります。

4
Ben

これは、ポインタデータが値によってsomeFunctionに渡されるためです。

int *data = NULL;
//data is passed by value here.
someFunction (data); 
//the memory allocated inside someFunction  is not available.

ポインターへのポインターまたは割り当てられたポインターを返すと、問題が解決します。

void someFunction (int **data) {
  *data = (int *) malloc (sizeof (data));
}


int*  someFunction (int *data) {
  data = (int *) malloc (sizeof (data));
return data;
}
2
aJ.

someFunction()は、パラメーターをint *として受け取ります。つまり、main()から呼び出すと、渡した値のコピーが作成されます。関数内で変更しているのはこのコピーなので、変更は外部に反映されません。他の人が示唆したように、int **を使用して変更をデータに反映させることができます。それを行う別の方法は、someFunction()からint *を返すことです。

2
Naveen

ダブルポインター技術の使用とは別に、1つだけの戻りパラメーターがある場合、必要な書き換えは次のとおりです。

 int *someFunction () {
   return (int *) malloc (sizeof (int *));
 }

そしてそれを使う:

 int *data = someFunction ();
2
Toad

次に、関数にメモリを割り当て、パラメータを介してポインタを返す一般的なパターンを示します。

_void myAllocator (T **p, size_t count)
{
  *p = malloc(sizeof **p * count);
}
...
void foo(void)
{
  T *p = NULL;
  myAllocator(&p, 100);
  ...
}
_

別の方法は、ポインターを関数の戻り値にすることです(私の推奨メソッド):

_T *myAllocator (size_t count)
{
  T *p = malloc(sizeof *p * count);
  return p;
}
...
void foo(void)
{
  T *p = myAllocator(100);
  ...
}
_

メモリ管理に関する注意事項:

  1. メモリ管理の問題を回避する最良の方法は、メモリ管理を回避することです。 本当に必要でない限り、動的メモリをいじらないでください。
  2. 1989 ANSI標準以前の実装を使用している場合、またはコードをC++としてコンパイルする場合を除き、malloc()の結果をキャストしないでください。 stdlib.hを含めることを忘れた場合、またはスコープにmalloc()のプロトタイプがない場合、戻り値をキャストすると、貴重なコンパイラー診断が抑制されます。
  3. データ型のサイズではなく、割り当てられているオブジェクトのサイズを使用します(つまり、sizeof (T)の代わりに_sizeof *p_)。これにより、データ型を変更する必要がある場合(たとえば、intからlongまたはfloatからdoubleに変更する場合)の胸焼けを節約できます。また、コードがIMOを少し読みやすくなります。
  4. 高レベルの割り当ておよび割り当て解除関数の背後にあるメモリ管理関数を分離します。これらは、割り当てだけでなく、初期化やエラーも処理できます。
1
John Bode

ダブルポインターを使用するのではなく、新しいポインターを割り当てて返すだけで済みます。ダブルポインターを渡す必要はありません。関数のどこでも使用されていないからです。

void *を返すので、あらゆるタイプの割り当てに使用できます。

void *someFunction (size_t size) {
    return  malloc (size);
}

そしてそれを次のように使用します:

int *data = someFunction (sizeof(int));
0
GG.

ここで、ポインタを変更しようとしています。つまり、 "data == Null"から "data == 0xabcd"に割り当てた他のメモリに変更しています。したがって、必要なデータを変更するには、データのアドレス、つまり&dataを渡します。

void someFunction (int **data) {
  *data = (int *) malloc (sizeof (int));
}
0
Learner

編集した追加の質問に返信する:

「*」は、何かへのポインタを示します。したがって、「**」は何かへのポインタへのポインタ、「***」は何かへのポインタへのポインタなどになります。

「データが関数パラメータでない場合」の「int **データ」の通常の解釈は、int配列のリストへのポインタです(たとえば、「int a [100] [100]」)。

したがって、最初にint配列を割り当てる必要があります(簡単にするために、malloc()への直接呼び出しを使用しています)。

data = (int**) malloc(arrayCount); //allocate a list of int pointers
for (int i = 0; i < arrayCount; i++) //assign a list of ints to each int pointer
   data [i] = (int*) malloc(arrayElemCount);
0
karx11erx