37

何かのために C に戻ってきましたが、このメモリ管理がどのように機能するかを思い出すのに苦労しています。構造体へのポインターの配列へのポインターが必要です。

私が持っているとしましょう:

struct Test {
   int data;
};

次に、配列:

struct Test **array1;

これは正しいです?私の問題は、このことで動作しています。したがって、配列内の各ポインターは、個別に割り当てられたものを指します。しかし、私はこれを最初に行う必要があると思います:

array1 = malloc(MAX * sizeof(struct Test *));

上記が理解できなくて困っています。これを行う必要がありますか? なぜこれを行う必要があるのですか? 特に、ポインタが指しているそれぞれのものにメモリを割り当てる場合、ポインタにメモリを割り当てるとはどういう意味ですか?

今、構造体へのポインターの配列へのポインターがあるとします。以前に作成したのと同じ配列を指すようにします。

struct Test **array2;

上記のようにポインター用のスペースを割り当てる必要がありますか、それとも単に行うことができますか?

array2 = array1
4

4 に答える 4

102

割り当てられた配列

割り当てられた配列を使用すると、従うのは簡単です。

ポインターの配列を宣言します。この配列の各要素は次を指しますstruct Test:

struct Test *array[50];

次に、必要に応じてポインタを構造体に割り当てて割り当てます。ループを使用するのは簡単です。

array[n] = malloc(sizeof(struct Test));

次に、この配列へのポインターを宣言します。

                               // an explicit pointer to an array 
struct Test *(*p)[] = &array;  // of pointers to structs

(*p)[n]->dataこれにより、 ;を使用できます。n 番目のメンバーを参照します。

この内容が混乱していても心配しないでください。それはおそらくCの最も難しい側面です。


ダイナミック リニア アレイ

構造体のブロック (事実上、構造体へのポインターではなく構造体の配列) を割り当て、そのブロックへのポインターが必要な場合は、より簡単に行うことができます。

struct Test *p = malloc(100 * sizeof(struct Test));  // allocates 100 linear
                                                     // structs

次に、このポインターを指すことができます。

struct Test **pp = &p

構造体へのポインターの配列はもうありませんが、全体が大幅に簡素化されます。


動的に割り当てられた構造体の動的配列

最も柔軟ですが、頻繁には必要ありません。最初の例と非常に似ていますが、追加の割り当てが必要です。これを実証するために、正常にコンパイルされる完全なプログラムを作成しました。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

struct Test {
    int data;
};

int main(int argc, char **argv)
{
    srand(time(NULL));

    // allocate 100 pointers, effectively an array
    struct Test **t_array = malloc(100 * sizeof(struct Test *));

    // allocate 100 structs and have the array point to them
    for (int i = 0; i < 100; i++) {
        t_array[i] = malloc(sizeof(struct Test));
    }

    // lets fill each Test.data with a random number!
    for (int i = 0; i < 100; i++) {
        t_array[i]->data = rand() % 100;
    }

    // now define a pointer to the array
    struct Test ***p = &t_array;
    printf("p points to an array of pointers.\n"
       "The third element of the array points to a structure,\n"
       "and the data member of that structure is: %d\n", (*p)[2]->data);

    return 0;
}

出力:

> p points to an array of pointers.
> The third element of the array points to a structure,
> and the data member of that structure is: 49

またはセット全体:

for (int i = 0; i < 100; i++) {
    if (i % 10 == 0)
        printf("\n");
    printf("%3d ", (*p)[i]->data);
}

 35  66  40  24  32  27  39  64  65  26 
 32  30  72  84  85  95  14  25  11  40 
 30  16  47  21  80  57  25  34  47  19 
 56  82  38  96   6  22  76  97  87  93 
 75  19  24  47  55   9  43  69  86   6 
 61  17  23   8  38  55  65  16  90  12 
 87  46  46  25  42   4  48  70  53  35 
 64  29   6  40  76  13   1  71  82  88 
 78  44  57  53   4  47   8  70  63  98 
 34  51  44  33  28  39  37  76   9  91 

単一動的に割り当てられた構造体の動的ポインター配列

この最後の例はかなり具体的です。前の例で見たように、これはポインターの動的配列ですが、それらとは異なり、要素はすべて単一の割り当てで割り当てられます。これには用途があり、元の割り当てをそのままにして、さまざまな構成でデータを並べ替える場合に最も注目に値します。

最も基本的な単一ブロックの割り当てで行うように、要素の単一ブロックを割り当てることから始めます。

struct Test *arr = malloc(N*sizeof(*arr));

次に、ポインターののブロックを割り当てます。

struct Test **ptrs = malloc(N*sizeof(*ptrs));

次に、ポインター リストの各スロットに、元の配列の 1 つのアドレスを入力します。ポインタ演算により要素から要素アドレスに移動できるため、これは簡単です。

for (int i=0;i<N;++i)
    ptrs[i] = arr+i;

この時点で、次の両方が同じ要素フィールドを参照しています。

arr[1].data = 1;
ptrs[1]->data = 1;

そして、上記を確認した後、その理由が明確になることを願っています.

ポインター配列と元のブロック配列の処理が完了すると、次のように解放されます。

free(ptrs);
free(arr);

ptrs[]注:配列内の各アイテムを個別に解放するわけではありません。それは彼らが割り当てられた方法ではありません。それらは単一のブロック ( で示されるarr) として割り当てられたので、解放する必要があります。

では、なぜ誰かがこれをやりたいのでしょうか? いくつかの理由。

まず、メモリ割り当て呼び出しの数が大幅に減少します。代わりに(ポインター配列用に 1 つ、個々の構造体用に N) 、配列ブロック用に 1 つ、ポインター配列用に 1 つの2 つN+1しかありません。メモリ割り当ては、プログラムが要求できる最も高価な操作の 1 つであり、可能な場合は最小限に抑えることが望ましいです (注: ファイル IO は別の情報です)。

別の理由: データの同じ基本配列の複数の表現。データを昇順と降順の両方で並べ替え、両方の並べ替えられた表現を同時に利用できるようにしたいとします。データ配列を複製することもできますが、それには大量のコピーが必要になり、大量のメモリが消費されます。代わりに、追加のポインター配列を割り当て、ベース配列からのアドレスを入力してから、そのポインター配列を並べ替えます。これは、ソートされるデータが大きい場合 (アイテムごとにおそらくキロバイト、またはそれ以上) に特に大きな利点があります。元のアイテムはベース配列の元の場所に残りますが、それらをソートできる非常に効率的なメカニズムが得られます。実際に動かさなくても彼ら。アイテムへのポインタの配列をソートします。アイテムはまったく移動しません。

これを理解するのは大変なことだと思いますが、ポインターの使用法は、C 言語で実行できる多くの強力な機能を理解するために不可欠です。そのため、本を読み、記憶をリフレッシュし続けてください。戻ってきます。

于 2013-03-13T22:45:40.750 に答える
5

他の人が示唆しているように、実際の配列を宣言する方が良いかもしれませんが、あなたの質問はメモリ管理に関するもののようですので、それについて説明します。

struct Test **array1;

これは、のアドレスへのポインタstruct Testです。(構造体自体へのポインターではありません。構造体のアドレスを保持するメモリー位置へのポインターです。)宣言はポインターにメモリーを割り当てますが、それが指す項目には割り当てません。配列にはポインタを介してアクセスできるため、*array1要素がタイプの配列へのポインタとして操作できますstruct Test。しかし、それが指す実際の配列はまだありません。

array1 = malloc(MAX * sizeof(struct Test *));

MAXこれにより、タイプのアイテムへのポインタを保持するためのメモリが割り当てられますstruct Test。この場合も、構造体自体にメモリを割り当てません。ポインタのリストのみ。arrayしかし、これで、割り当てられたポインターの配列へのポインターとして扱うことができます。

を使用するarray1には、実際の構造体を作成する必要があります。これを行うには、各構造体を次のように宣言するだけです。

struct Test testStruct0;  // Declare a struct.
struct Test testStruct1;
array1[0] = &testStruct0;  // Point to the struct.
array1[1] = &testStruct1;

ヒープに構造体を割り当てることもできます。

for (int i=0; i<MAX; ++i) {
  array1[i] = malloc(sizeof(struct Test));
}

メモリを割り当てたら、同じ構造体のリストを指す新しい変数を作成できます。

struct Test **array2 = array1;

array2に割り当てたのと同じメモリを指すため、追加のメモリを割り当てる必要はありませんarray1


ポインタのリストへのポインタが必要な場合もありますが、何か凝ったことをしているのでない限り

struct Test *array1 = malloc(MAX * sizeof(struct Test));  // Pointer to MAX structs

これはポインタを宣言し、構造体にarray1十分なメモリを割り当て、そのメモリを指します。これで、次のような構造体にアクセスできます。MAXarray1

struct Test testStruct0 = array1[0];     // Copies the 0th struct.
struct Test testStruct0a= *array1;       // Copies the 0th struct, as above.
struct Test *ptrStruct0 = array1;        // Points to the 0th struct.

struct Test testStruct1 = array1[1];     // Copies the 1st struct.
struct Test testStruct1a= *(array1 + 1); // Copies the 1st struct, as above.
struct Test *ptrStruct1 = array1 + 1;    // Points to the 1st struct.
struct Test *ptrStruct1 = &array1[1];    // Points to the 1st struct, as above.

では、違いは何ですか?いくつかのこと。明らかに、最初の方法では、ポインタにメモリを割り当ててから、構造体自体に追加のスペースを割り当てる必要があります。malloc()2つ目は、1回の呼び出しで逃げることができます。余分な仕事はあなたに何を買いますか?

最初のメソッドは構造体へのポインタの実際の配列を提供するため、各ポインタはメモリ内のTest任意の構造体を指すことができます。Testそれらは隣接している必要はありません。さらに、必要に応じて実際の構造体ごとにメモリを割り当てたり解放したりTest、ポインタを再割り当てしたりできます。したがって、たとえば、ポインタを交換するだけで2つの構造を交換できます。

struct Test *tmp = array1[2];  // Save the pointer to one struct.
array1[2] = array1[5];         // Aim the pointer at a different struct.
array1[5] = tmp;               // Aim the other pointer at the original struct.

一方、2番目の方法では、すべての構造体に単一の連続したメモリブロックを割り当て、Testそれをアイテムに分割しMAXます。また、配列内の各要素は固定位置にあります。2つの構造を交換する唯一の方法は、それらをコピーすることです。

ポインターはCで最も有用な構成の1つですが、理解するのが最も難しいものの1つでもあります。Cを引き続き使用する場合は、ポインター、配列、およびデバッガーに慣れるまで、時間をかけて操作することをお勧めします。

幸運を!

于 2013-03-13T23:18:20.620 に答える
2

構造体は、他のオブジェクトと大差ありません。文字から始めましょう:

char *p;
p = malloc (CNT * sizeof *p);

*p は文字なので、sizeof *psizeof (char) == 1; CNT 文字を割り当てました。次:

char **pp;
pp = malloc (CNT * sizeof *pp);

*p は文字へのポインタであり、sizeof *ppsizeof (char*) も同様です。CNT ポインターを割り当てました。次:

struct something *p;
p = malloc (CNT * sizeof *p);

*p は何かの構造体であり、sizeof *psizeof (何かの構造体) もそうです。CNT 構造体の何かを割り当てました。次:

struct something **pp;
pp = malloc (CNT * sizeof *pp);

*pp は struct へのポインターであり、sizeof *ppsizeof (struct something*) も同様です。CNT ポインターを割り当てました。

于 2013-03-14T00:17:49.770 に答える
2

タイプのレイヤーを作成するために typdefs を使用して一度にレイヤーを作成することをお勧めします。そうすることで、必要なさまざまなタイプがより明確になります。

例えば:

typedef struct Test {
   int data;
} TestType;

typedef  TestType * PTestType;

これにより、構造体用と構造体へのポインター用の 2 つの新しい型が作成されます。

次に、構造体の配列が必要な場合は、次を使用します。

TestType array[20];  // creates an array of 20 of the structs

構造体へのポインターの配列が必要な場合は、次を使用します。

PTestType array2[20];  // creates an array of 20 of pointers to the struct

次に、構造体を配列に割り当てたい場合は、次のようにします。

PTestType  array2[20];  // creates an array of 20 of pointers to the struct
// allocate memory for the structs and put their addresses into the array of pointers.
for (int i = 0; i < 20; i++) {
    array2 [i] = malloc (sizeof(TestType));
}

C では、ある配列を別の配列に割り当てることはできません。代わりにループを使用して、一方の配列の各要素を他方の要素に割り当てる必要があります。

編集: 別の興味深いアプローチ

別のアプローチは、いくつかのものをカプセル化する、よりオブジェクト指向のアプローチです。たとえば、タイプの同じレイヤーを使用して、2 つのタイプを作成します。

typedef struct _TestData {
    struct {
        int myData;   // one or more data elements for each element of the pBlob array
    } *pBlob;
    int nStructs;         // count of number of elements in the pBlob array
} TestData;

typedef TestData *PTestData;

次に、適切な名前の十分な名前のオブジェクトを作成するために使用するヘルパー関数がありますCreateTestData (int nArrayCount)

PTestData  CreateTestData (int nCount)
{
    PTestData ret;

    // allocate the memory for the object. we allocate in a single piece of memory
    // the management area as well as the array itself.  We get the sizeof () the
    // struct that is referenced through the pBlob member of TestData and multiply
    // the size of the struct by the number of array elements we want to have.
    ret = malloc (sizeof(TestData) + sizeof(*(ret->pBlob)) * nCount);
    if (ret) {   // make sure the malloc () worked.
            // the actual array will begin after the end of the TestData struct
        ret->pBlob = (void *)(ret + 1);   // set the beginning of the array
        ret->nStructs = nCount;           // set the number of array elements
    }

    return ret;
}

これで、以下のソース コード セグメントのように、新しいオブジェクトを使用できます。CreateTestData() から返されたポインターが有効であることを確認する必要がありますが、これは何ができるかを示すためのものです。

PTestData  go = CreateTestData (20);
{
    int i = 0;
    for (i = 0; i < go->nStructs; i++) {
        go->pBlob[i].myData = i;
    }
}

真に動的な環境では、オブジェクトに含まれる配列のサイズを変更するために、オブジェクトReallocTestData(PTestData p)を再割り当てする関数が必要になる場合もあります。TestData

このアプローチでは、特定の TestData オブジェクトを使い終わったら、オブジェクトをそのまま解放するだけでfree (go)、オブジェクトとその配列の両方が同時に解放されます。

編集:さらに拡張する

このカプセル化された型を使用して、他にもいくつかの興味深いことができるようになりました。PTestType CreateCopyTestData (PTestType pSrc)たとえば、新しいインスタンスを作成し、引数を新しいオブジェクトにコピーするコピー関数を使用できます。PTestType CreateTestData (int nCount)次の例では、コピーするオブジェクトのサイズを使用して、型のインスタンスを作成する関数を再利用します。新しいオブジェクトを作成した後、ソース オブジェクトからデータのコピーを作成します。最後の手順は、ソース オブジェクトでそのデータ領域を指しているポインターを修正して、新しいオブジェクトのポインターが古いオブジェクトのデータ領域ではなく、それ自体のデータ領域を指すようにすることです。

PTestType CreateCopyTestData (PTestType pSrc)
{
    PTestType pReturn = 0;

    if (pSrc) {
        pReturn = CreateTestData (pSrc->nStructs);

        if (pReturn) {
            memcpy (pReturn, pSrc, sizeof(pTestType) + pSrc->nStructs * sizeof(*(pSrc->pBlob)));
            pReturn->pBlob = (void *)(pReturn + 1);   // set the beginning of the array
        }
    }

    return pReturn;
}
于 2013-03-13T22:42:29.983 に答える