sourcecode

C: 구조물에 대한 포인터 배열에 대한 포인터(할당/할당 취소 문제)

copyscript 2023. 7. 23. 14:36
반응형

C: 구조물에 대한 포인터 배열에 대한 포인터(할당/할당 취소 문제)

저는 무언가를 위해 C에 다시 들어갔지만, 이 메모리 관리가 어떻게 작동하는지 대부분 기억하는 데 어려움을 겪고 있습니다.구조물에 대한 포인터 배열에 대한 포인터를 갖고 싶습니다.

내가 가지고 있다고 말하시오:

struct Test {
   int data;
};

그런 다음 배열:

struct Test **array1;

이거 맞는건가요?제 문제는 이것을 다루는 것입니다.따라서 배열의 각 포인터는 개별적으로 할당된 것을 가리킵니다.하지만 제가 먼저 해야 할 일이 있습니다.

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

위의 내용을 이해하는 데 어려움을 겪고 있습니다.이 작업을 수행해야 하며, 왜 이 작업을 수행해야 하는 이유는 무엇입니까?특히 포인터가 가리키는 각 항목에 메모리를 할당하려면 포인터에 메모리를 할당한다는 것은 무엇을 의미합니까?

구조물에 대한 포인터 배열에 대한 포인터를 가지고 있다고 말합니다.이제 이전에 만든 어레이와 동일한 어레이를 가리키도록 하겠습니다.

struct Test **array2;

위에서 했던 것처럼 포인터를 위한 공간을 할당해야 합니까? 아니면 그냥 할 수 있습니까?

array2 = array1

할당된 배열

할당된 배열을 사용하면 충분히 쉽게 추적할 수 있습니다.

포인터 배열을 선언합니다.의 각 는 이배의각요다가리니다킵음을는소열을 .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]->datan번째 멤버를 참조합니다.

이 물건이 헷갈리더라도 걱정하지 마세요.그것은 아마도 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));

그런 다음 포인터 목록의 각 슬롯을 원래 배열 중 하나의 주소로 채웁니다.포인터 산술을 사용하면 요소에서 요소 주소로 이동할 수 있으므로 이는 간단합니다.

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), 그리고 그것이 그들이 해방되어야 하는 방법입니다.

그런데 왜 누군가가 이걸 하고 싶어할까요?몇 가지 이유.

첫째, 메모리 할당 호출 수를 획기적으로 줄입니다. 히려오.N+1(포인터 배열의 경우 하나, 개별 구조의 경우 N) 이제 배열 블록의 경우 하나, 포인터 배열의 경우 하나만 있습니다.메모리 할당은 프로그램이 요청할 수 있는 가장 비싼 작업 중 하나이며, 가능하면 최소화하는 것이 좋습니다(참고: 파일 IO는 다른 작업입니다. fyi).

또 다른 이유:동일한 기본 데이터 배열에 대한 다중 표현입니다.데이터를 오름차순과 내림차순으로 정렬하고 정렬된 표현을 동시에 사용할 수 있도록 한다고 가정합니다.데이터 어레이를 복제할 수는 있지만, 이 경우 많은 양의 복사가 필요하고 메모리 사용량이 상당합니다.대신 추가 포인터 배열을 할당하고 기본 배열의 주소로 채운 다음 해당 포인터 배열을 정렬합니다.이는 정렬되는 데이터가 클 때(항목당 킬로바이트 또는 그보다 더 클 때) 특히 중요한 이점이 있습니다.원래 항목은 기본 배열의 원래 위치에 남아 있지만, 이제 실제로 항목을 이동하지 않고도 정렬할 수 있는 매우 효율적인 메커니즘을 갖게 되었습니다.항목에 대한 포인터 배열을 정렬하면 항목이 전혀 이동되지 않습니다.

저는 이것이 받아들이기 힘든 것이라는 것을 알지만, 포인터 사용은 당신이 C 언어로 할 수 있는 많은 강력한 것들을 이해하는 데 매우 중요합니다. 그러니 책을 읽고 계속 기억을 새롭게 하세요.그것은 돌아올 것입니다.

다른 사람들이 제안한 것처럼 실제 어레이를 선언하는 것이 더 나을 수도 있지만, 당신의 질문은 메모리 관리에 관한 것으로 보이므로 저는 그것에 대해 논의하겠습니다.

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메모동메일가모리를당한다니킵리리할한와가▁points킵리니▁you다▁▁same▁to▁to'▁the에 할당한 메모리와 동일한 메모리를 가리킵니다.array1.


때때로 포인터 목록에 대한 포인터를 갖고 싶지만, 화려한 것을 하지 않는 한

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

포인터가 됩니다.array1에 충분한 할당MAX점 구물, 및array1그 기억까지.이제 다음과 같은 구조에 액세스할 수 있습니다.

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()전화입니다. 남는 일은 무엇을 사시나요?

첫 번째 방법이 실제 포인터 배열을 제공하기 때문입니다.Test구조체, 각 포인터는 임의의 것을 가리킬 수 있습니다.Test구조, 메모리의 모든 곳에서, 연속적일 필요는 없습니다.를 각 메모리에 하여 자유롭게 할 수 .Test필요에 따라 구조를 지정하고 포인터를 재할당할 수 있습니다.예를 들어 포인터를 교환하는 것만으로 두 개의 구조를 바꿀 수 있습니다.

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.

반면에, 두 번째 방법은 모든 것을 위해 하나의 연속적인 메모리 블록을 할당합니다.Test으로 분할합니다.MAX그리고 어레이의 각 요소는 고정된 위치에 상주합니다. 두 구조를 교환하는 유일한 방법은 복사하는 것입니다.

포인터는 C에서 가장 유용한 구조 중 하나이지만 가장 이해하기 어려운 구조일 수도 있습니다.만약 당신이 C를 계속 사용할 계획이라면, 당신이 그것들에 익숙해질 때까지 포인터, 배열 및 디버거를 가지고 노는 데 시간을 보내는 것은 아마도 가치 있는 투자일 것입니다.

행운을 빕니다.

typdefs를 사용하여 한 번에 레이어를 작성하여 레이어를 작성하는 것이 좋습니다.그렇게 함으로써, 필요한 다양한 유형이 훨씬 더 명확해질 것입니다.

예를 들어:

typedef struct Test {
   int data;
} TestType;

typedef  TestType * PTestType;

이렇게 하면 구조체용과 구조체에 대한 포인터용으로 두 가지 유형이 새로 만들어집니다.

다음으로 구조체 배열을 원할 경우 다음을 사용합니다.

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에서는 한 어레이를 다른 어레이에 할당할 수 없습니다.대신 루프를 사용하여 한 배열의 각 요소를 다른 배열의 요소에 할당해야 합니다.

편집: 또 다른 흥미로운 접근 방식

또 다른 접근 방식은 몇 가지 사항을 캡슐화하는 보다 객체 지향적인 접근 방식입니다.예를 들어 동일한 유형의 계층을 사용하여 두 가지 유형을 만듭니다.

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)새 인스턴스를 만든 다음 인수를 새 개체에 복사합니다.에▁function 기능을 재사용합니다.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;
}

구조는 다른 객체와 크게 다르지 않습니다.캐릭터부터 시작하겠습니다.

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

이기 때문에 *p 문 자로므이는,,sizeof *p는 (char) == 1의 크기이며, CNT 문자를 할당했습니다. 예:

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

문자에 이므로 *p는 문자에 대한 포인터입니다.sizeof *pp크기는 (문자*)입니다.우리는 CNT 포인터를 할당했습니다. 예:

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

이기 때문에 *p 것는이다인구, 그서래조.sizeof *p의 크기입니다.우리는 CNT 구조를 할당했습니다. 예:

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

struct에 이므로 *pp는 구조대한포므로이인터에체,sizeof *pp의 크기입니다.우리는 CNT 포인터를 할당했습니다.

언급URL : https://stackoverflow.com/questions/15397728/c-pointer-to-array-of-pointers-to-structures-allocation-deallocation-issues

반응형