0

私はCの基本を学び、現在、malloc()を使用しています。ユーザーに入力を求め、構造体に上記のデータを入力し、その構造体を配列に保存する関数があるとします(すべてメイン関数からの参照によって渡されます)。

Valgrindを介してプログラムを実行すると、「まだ到達可能な」バイトが取得されます(これは問題ありませんか?リークとは見なされません)が、保存されたデータはすべて失われたブロックになります。

プログラムの実行が終了した後、どうすればそのメモリを解放できますか?また、いくつかのことを明確にするために、コード内にいくつかの質問があります。誰かが私にそれらを説明していただければ幸いです。

これが私がやろうとしていることに似たコードです:

私は次の構造体を宣言しています:

struct Person {
  char name[MAX_INPUT];
  int age;
};

私は次のような関数を書いています:

int function2(struct Person *list, int *index) {
  struct Person *prsn = malloc(sizeof(struct Person)); 
  // !Why do we sometimes cast the malloc or not? 
  // I sometimes get errors when I do, sometimes when I don't, 
  // while the surrounding code is pretty much the same.
  assert(prsn != NULL);

  // User input code goes here ... 

  // Now to save the Person created
  strcpy(prsn->name, nameInput);
  prsn->age = ageInput;
  list[(*index)++] = *prsn; 
  // !Why use the dereferencing *prsn here? 
  // why not directly prsn? Or is that saving the memory address and not very useful.

  return 0;
}

そしてこれが私の主な機能です:

int main(int argc, char *argv[]) { 
  struct Person personList[MAX_SIZE];
  int index;

  function2(personList, &index);

  // Before closing, I want to free any mallocs I have done here. free()

  return 0;
}

Valgrindレポート:

LEAK SUMMARY:
==1766==    definitely lost: 44 bytes in 1 blocks
==1766==    indirectly lost: 0 bytes in 0 blocks
==1766==      possibly lost: 0 bytes in 0 blocks
==1766==    still reachable: 10,355 bytes in 34 blocks
==1766==         suppressed: 0 bytes in 0 blocks

前もって感謝します。

編集:function2パラメーター、returnなどを修正しました。申し訳ありませんが、メモリの解放に関する私の主な質問を説明するために、すぐにそれを書いていました。修正のヒントをありがとうございますが、実際のコードは実際には正しくコンパイルされています。

Edit2:free()の使用を提案するように、メインの最後に単純なループを追加すると、次のエラーが発生します。

==2216== LEAK SUMMARY:
==2216==    definitely lost: 44 bytes in 1 blocks
==2216==    indirectly lost: 0 bytes in 0 blocks
==2216==      possibly lost: 0 bytes in 0 blocks
==2216==    still reachable: 10,355 bytes in 34 blocks
==2216==         suppressed: 0 bytes in 0 blocks
==2216== 
==2216== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==2216== 
==2216== 1 errors in context 1 of 2:
==2216== Invalid free() / delete / delete[] / realloc()
==2216==    at 0x563A: free (in /usr/local/Cellar/valgrind/3.8.1/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==2216==    by 0x10000194E: main (in ./test.out)
==2216==  Address 0x7fff5fbf9dd0 is on thread 1's stack
==2216== 
==2216== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
4

3 に答える 3

7

コード分​​析

これを少しずつ分析してみましょう。

int function2(struct Person *list) {

Iserniが彼の回答で指摘したように、この定義は関数の呼び出しと矛盾しています。私は、既存のコードに対する彼の修正に概ね同意します (ただし、すぐに修正することをお勧めします)。

int function2(struct Person *list, int *index)


  struct Person *prsn = malloc(sizeof(struct Person)); 
  // !Why do we sometimes cast the malloc or not? 
  // I sometimes get errors when I do, sometimes when I don't, 
  // while the surrounding code is pretty much the same.

それは、C と C++ のどちらを使用しているかによって異なります。C++ では、からの戻り値をキャストする必要がありmalloc()ます (使用する場合malloc()、通常は C++ では使用しないでください)。C では、キャストはオプションです。キャストを省略するとエラーが明らかになり、キャストを挿入すると隠れるという考え方があります。私はそれに同意しません。それmalloc()は経由<stdlib.h>で宣言されるべきであり、スコープに宣言がない場合、コンパイラは警告する必要があり、スコープに宣言がある場合、キャストは罪を隠すことができないと思います。もう 1 つの考えられる問題は、ポインタを非ポインタに割り当てることです。これは、コンパイラーも文句を言うべき間違いです。

  assert(prsn != NULL);

これは通常、メモリ割り当てエラーを処理する賢明な長期的な方法とは見なされません。

  // User input code goes here ... 

  // Now to save the Person created
  strcpy(prsn->name, nameInput);
  prsn->age = ageInput;
  list[(*index)++] = *prsn; 
  // !Why use the dereferencing *prsn here?

なぜなら:

  • listですstruct Person *
  • したがってlist[i]struct Person(と綴らiれていますが(*index)++)です。
  • したがって、それに a を割り当てる必要がありますstruct Person
  • prsnですstruct Person *
  • したがって*prsn、 もstruct Personです。
  • したがって、割り当ては「正しい」です。
  • それはまたあなたに漏れを与えます。
  • list[i]のコンテンツをのコンテンツで上書きしました*prsn
  • prsnポインターをどこにも保存していません。
  • そのため、関数から戻るときにメモリ リークが発生します。

これを修正するために必要な手術は無視できません。

int function2(struct Person **item)
...
*item = prsn;

呼び出しを修正する必要があります。解剖するときに戻ってきますmain()

  // why not directly prsn? Or is that saving the memory address and not very useful.
}

あなたの関数は を返すように宣言されてintいますが、 no を示していますreturnvoid値を返さない場合は、特にコードのように戻り値を無視する場合は、関数を として宣言しますmain()

あなたの最後のコメントは、ほとんど上記の議論でカバーされています。メモリアドレスを保存することは、リークを止めるために重要であるため、非常に便利です。

そして、これが私の主な機能です:

int main(int argc, char *argv[]) { 
  struct Person personList[MAX_SIZE];
  int index;

初期化されていない変数を使用することは悪いニュースです。せいぜい誤ってゼロになっただけです。ランダムな値を配列インデックスとして使用する余裕はありません。明示的に初期化します。また、構造体の配列ではなく、ポインターの配列が必要になります。

  struct Person *personList[MAX_SIZE];
  int index = 0;

  ...other code...

  function2(personList, &index);

改訂された機能では:

  function2(&personList[index++]);

これは望ましいことです。function2()これは、配列について知る必要がないことを意味します。割り当てられたメモリ ポインターを割り当てる必要があるポインターのアドレスを渡すだけです。main()これにより、関数との間の結合が減少しfunction2()、コードが全体的に単純になります。

  // Before closing, I want to free any mallocs I have done here. free()

だからあなたは書く:

  for (int i = 0; i < index; i++)
      free(personList[i]);

これにより、割り当てられたすべてのメモリが解放されます。

  return 0;
}

main()C99 は 100% 必要ではないと言っていますが、私は の最後に明示的な return があるのが好きです。

十分な警告を有効にしてコンパイルしていることを確認してください。GCC を使用している場合はgcc -Wall、最小レベルのコンパイル警告を実行する必要があります (実行時にコードに警告が表示されないようにする必要があります)。私はより厳しい警告で実行します:gcc -std=c99 -Wall -Wextra -Wmissing-prototypes -Wold-style-definition -Wstrict-prototypes -Wshadow通常. いくつかの警告を得るには、含める-O3(またはある程度の最適化を行う) 必要があります。プロトタイプに関するものは、私が使用している古いコードベースについてのパラノイアを反映していますが、そのコードベースにはまだ K&R 関数定義が含まれています。


コメントへの回答

詳細を調べて試してみるときの最初の質問: 構造体の配列とポインターの配列の間にメモリへの影響はありますか?

はい、しかしそれはあなたが考えていることではないかもしれません。構造体の配列を使用する場合、構造体に動的にメモリを割り当てる必要はありません。これは、コンパイラが既に構造体を割り当てているためです。演習の目的はポインターと を使用するmalloc()ことなので、ポインターを使用する方が適切です。スペースに関しては、ポインタの配列で使用される合計メモリがわずかに多くなります (ただし、メモリ リークは少なくなります)。

ポインターの配列を使用するようにコードを変更しようとしています。しかし、現在function2(personList, &index);call を使用するとfunction2、次の警告が表示されます: incompatible pointer types passing 'struct Person *[512]' to parameter of type 'struct Person *'. 詳細を説明するために、主な質問に追加のコードを記述しても問題ありませんか? 注意として、プログラムが一時的に関数から関数にデータをコピーしないように、変数をできるだけ参照するようにしています。

すべての変更を行っていない場合、コンパイラは正しいです。2 つの引数を使用するコードは、1 つの引数を使用するコードよりも多くのデータを関数間でコピーします。

バージョン 1

次のプログラムは、提案された単一引数を使用して、関数 とfunction2()の間の結合を減らし、両方を単純化します。main()function2()

このコードは、コマンド ラインを使用して、Mac OS X 10.7.5 上の GCC 4.7.1 で警告なしでコンパイルされます。

    gcc -O3 -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
        -Wold-style-definition mem.c -o mem

で実行するvalgrindと、メモリリークは発生しません。

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

enum { MAX_INPUT = 28 };
enum { MAX_SIZE  = 3  };

struct Person
{
  char name[MAX_INPUT];
  int age;
};

static void function2(struct Person **list)
{
    struct Person *prsn = malloc(sizeof(struct Person)); 
    assert(prsn != NULL);

    char *nameInput = "This is my name";
    int ageInput = 29;    // Again!

    strcpy(prsn->name, nameInput);
    prsn->age = ageInput;
    *list = prsn;
}

int main(void)
{
    struct Person *personList[MAX_SIZE];
    int index = 0;

    function2(&personList[index++]);
    function2(&personList[index++]);
    function2(&personList[index++]);

    for (int i = 0; i < index; i++)
        free(personList[i]);

    return 0;
}

バージョン 2

これは の引数が 2 つのバージョンを保持し、それ自体で行うべきfunction2()カウントを行います。main()このプログラムはまた、 の下できれいにコンパイルされ、きれいに実行されvalgrindます。

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

enum { MAX_INPUT = 28 };
enum { MAX_SIZE  = 3  };

struct Person
{
  char name[MAX_INPUT];
  int age;
};

static void function2(struct Person **list, int *index)
{
    struct Person *prsn = malloc(sizeof(struct Person)); 
    assert(prsn != NULL);

    char *nameInput = "This is my name";
    int ageInput = 29;    // Again!

    strcpy(prsn->name, nameInput);
    prsn->age = ageInput;
    list[(*index)++] = prsn;
}

int main(void)
{
    struct Person *personList[MAX_SIZE];
    int index = 0;

    function2(personList, &index);
    function2(personList, &index);
    function2(personList, &index);

    for (int i = 0; i < index; i++)
        free(personList[i]);

    return 0;
}
于 2012-10-22T00:13:33.970 に答える
1

私はあなたfunction2が本当にだと思います

int function2(struct Person *list, int *index)

そこにmallocされたポインタを保存します

list[(*index)++] = prsn;

次にmain、リストを解放します

while(index)
    free(list[--index]);
于 2012-10-21T23:36:49.170 に答える
0

オブジェクト指向プログラミング言語では、オブジェクトの配列は実際にはオブジェクトへのポインターの配列にすぎません。したがって、ポインターが 4 バイト、オブジェクトが 5 バイトの場合、10 個のオブジェクトの配列は、実際には 4*10 バイトの長さになります (さらにオーバーヘッド)。

MyClass[] my_variable = new MyClass[10]; // this will allocate 10*pointersize bytes, plus overhead, you still need to allocate more space for the objects themselves

C では、構造体の配列は構造体の配列です。構造体が 8 バイトの場合、それらを 10 個並べると 80 バイトになります。さらに多くのスペースを割り当てる必要はありません。それはすでにそこにあります。

struct data_t my_variable[10]; // this will allocate 10*sizeof(data_t) bytes

したがって、簡単な答えは次のとおりです。

int function2(struct Person *list, int *index) {
  char* nameInput;
  // User input code goes here ...
  // IMPORTANT: the user input code must allocate the space for nameInput
  // if it doesn't, then a copy of the buffer must be made
  // instead of the direct assignment below

  // the memory for this instance of the person was already created when the
  // array was created, so just save the values

  list[*index].name = nameInput;
  list[*index].age = ageInput;
  *index += 1; 

  return 0;
}

コードがこの行を実行すると:

list[(*index)++] = *prsn; 

これは文字通り 8 バイト (32 ビット マシンでは sizeof(int) + sizeof(char*)) をコピーしています。

malloc で割り当てられたメモリへのアドレスはその行にコピーされていませんが、メモリの内容はコピーされています。これがメモリ リークである理由です。関数が終了すると、アドレスが配列に入れられなかったため、アドレスが失われます。

于 2013-09-15T08:15:10.320 に答える