アプリ版:「スタンプのみでお礼する」機能のリリースについて

どうにも動的確保について間違っている気がするのでお尋ねいたします。
よくメモリを動的に確保する場合に私は次のようなプログラムを書きます

ポインタを用意する(例えばint *p)
mallocでメモリ確保
ポインタを関数に渡す(Func(p))
関数側でreallocし、値を代入する(p=(int *)realloc(p,sizeof(int)*num))
関数呼び出し側で、その値を使う

しかし、この方法を使うとどうにも関数を呼び出した際のポインタ(p)と、reallocした後のポインタ(p)の値が違うことがあり、値が不定になることがあります。
(reallocのメモリの確保の仕方のせいでしょうか)
この使い方は恐らくどこか間違っていると思うんですが、いまいち納得のいく解決策が思いつきません。
例えばポインタを引数ではなく戻り値として得ればできますが、2つ以上のポインタについてはできません。

何卒ご教授のほどをよろしくお願いいたします。

A 回答 (5件)

まず、p=(int *)realloc(p,sizeof(int)*num);


で確保された領域が、それ以前の p と同じであることは保障されていません。
realloc() で保障されているのは、「新旧の領域の、短い長さまでの内容は同じである」ということだけです。

この場合、ポインタの値自体は、値渡しになりますから、例えば、

void func(int **ptr);

int *p = malloc();
// ただし、関数内部で realloc() する前提なら、
// 呼び出し前に、確保する必要性はないかも
func(&p);
....

void func(int **ptr)
{
*ptr = realoc();
...
}

でいけたような気がします(未チェック)

あと、最近は、
(p=(int *)realloc(p,sizeof(int)*num))
と alloc() 系の関数の返値をキャストする必要はありません。
大昔は、alloc() 系は char * を返していましたが、今は、 void * を返すので、これは、キャストなしで任意のポインタに安全に変換できます。

この回答への補足

ありがとうございます。
なるほど、**ptrを使うことで関数呼び出し側にも反映されるんですね。

ただ、テストプログラムを作ってみると、何やらまた動作がおかしいみたいです
おかしい部分を抜き出したソースは次のとおりです

int main()
{
 int **p;
 int i;

 p = (int **)malloc(sizeof(int *));
 *p = (int *)malloc(sizeof(int));

 for(i=1;i<10;i++){
  *p = (int *)realloc(*p,sizeof(int)*(i+1));
  *p[i] = i;
 }
 free(*p);
 return 0;
}

このようにするとreallocがメモリ領域を拡張してくれなく(?)、*p[i] = i;の部分でエラー終了します。
主題が変わって(?)しまって恐縮ですが、まだ何かおかしいのでしょうか。
もし宜しければお願いいたします。

補足日時:2007/06/19 17:27
    • good
    • 0

メイン側でmallocしたのに、関数側でもreallocするのですか?


それなら、メイン側ではポインタだけ提示して、関数側でmallocすればよいのではないでしょうか?
C言語から離れて10余年なので、間違っているかもしれませんが(笑)

main(){
int *p;
// ポインタを関数に渡す(ポインタにmallocされたアドレスが欲しいのでポインタのポインタを渡す)
Func(&p);
}

Func(int **p){
*p = malloc(~);
}
    • good
    • 0
この回答へのお礼

メイン側でしていたのは、Func(p)の場合だとしていない場合エラーしてしまうためでした。
&p、**pを使うところまでは分かりました。
あとはrealloc・・・
別件で再質問することにいたします。

ありがとうございました。

お礼日時:2007/06/19 18:19

>ポインタを関数に渡す(Func(p))


提示されたコードはやりがちですが、
非常に危険で間違ったプログラムです。

>2つ以上のポインタについてはできません。
これは
int** sub( void );
のようなことを言っているのでしょうか?
であれば可能ですが今回はあまり触れません。

以下は危険なコードサンプル
#include <stdio.h>
#include <stdlib.h>
int sub( int* p );
int main( int argc, char* argv[] )
{
 int ret;
 int *alloc;
 //allocをわざと初期化していません。
 ret = sub( alloc );
 if( ret ){
  return -1;
 }
 //allocの中身は変わっていない為、不定
 printf( "alloc = %#x\n", alloc );
 //不定領域を開放
 free( alloc );
 return 0;
}

int sub( int* p )
{
 //pはallocの『中身』
 //pに確保領域のアドレスが入る
 p = (int*)malloc( sizeof(int)*10 );
 if( !p ){
  return -1;
 }
 printf( "p = %#x\n", p );
 //関数終了と共にpが破棄される
 return 0;
}

上記のように引数に対して戻り値を設定する場合、
ポインタを渡さなければなりません。
上記のコードを修正するならば、
以下のようになります。
#include <stdio.h>
#include <stdlib.h>
int sub( int** p );
int main( int argc, char* argv[] )
{
 int ret;
 int *alloc;

 //allocへのポインタを渡す
 ret = sub( &alloc );
 if( ret ){
  return -1;
 }
 //allocの中身は確保された領域へのポインタ
 printf( "alloc = %#x\n", alloc );
 //正しい場所を開放
 free( alloc );

 return 0;
}

int sub( int** p )
{
 //pはallocへのポインタ
 //allocへのポインタの中身に確保領域のアドレスが入る
 *p = (int*)malloc( sizeof(int)*10 );
 if( !p ){
  return -1;
 }
 printf( "p = %#x\n", *p );

 //関数終了と共にpが破棄される
 return 0;
}

上記のコードはインデントを全角スペースにしているので、
コンパイルする時は半角に置換してください。
    • good
    • 0
この回答へのお礼

ありがとうございます。
参考になりました。

2つというのは語弊がありました。
2つ以上のポインタについて、reallocしたい場合と言う意味です

お礼日時:2007/06/19 18:21

#3です。



サンプルコードのコメントが
間違っていたので訂正します。
//pはallocの『中身』
 ↓
//pはallocのコピー
もう一つ。
//pはallocへのポインタ
 ↓
//pはallocへのポインタのコピー
です。
参照渡しとか言われますが、厳密には、
C言語では、ポインタを値渡しします。
    • good
    • 0

> ポインタを用意する(例えばint *p)


> mallocでメモリ確保
> ポインタを関数に渡す(Func(p))
> 関数側でreallocし、値を代入する(p=(int *)realloc(p,sizeof(int)*num))
> 関数呼び出し側で、その値を使う

この場合、Func内部で常にメモリを解放するか、返却値等でpの値を返す必要がありますね。

> しかし、この方法を使うとどうにも関数を呼び出した際のポインタ(p)と、reallocした後のポインタ(p)の値が違うことがあり、値が不定になることがあります。

reallocでは、内部で新しいメモリブロックを割り付けることがありますので、返却値が元のポインタと同じ値になるとは限りません。通常、メモリブロックのサイズを広げるときには、ポインタの値が変わります。
なお、reallocで再割り付けを行うときは次にようにします。

int *t = realloc(p, size);
if (t != NULL)
 p = t;
else
 /* エラー処理 */

こうしないと、reallocが割り付けに失敗したとき、元のポインタの値が失われ、結果としてメモリリークにつながります。
また、C言語ではmallocやreallocの返却値はキャスト不要ですが、C++ではキャストが必須ですので、必要に応じて読み替えてください。ちなみに、C++ではreallocを使うよりstd::vectorなどを使う方が便利です。

> 例えばポインタを引数ではなく戻り値として得ればできますが、2つ以上のポインタについてはできません。

複数を扱う場合は、返却値にするより、結果を格納する配列を引数として渡した方が便利です。というか、元の値を配列で渡して、変化後の値を同じ配列に入れて返してあげればよいのです。
    • good
    • 0
この回答へのお礼

ありがとうございます。
参考になりました。

エラー処理については気をつけたいと思います。

お礼日時:2007/06/19 18:22

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!