C语言 C:指向结构指针数组的指针(分配/解除分配问题)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15397728/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 05:41:59  来源:igfitidea点击:

C: pointer to array of pointers to structures (allocation/deallocation issues)

cpointersmemorymemory-management

提问by DillPixel

I've been getting back into C for something, but I'm having trouble remembering much of how this memory management works. I'd like to have a pointer to an array of pointers to structures.

我一直在重新使用 C 语言,但是我很难记住这种内存管理是如何工作的。我想要一个指向结构指针数组的指针。

Say I have:

说我有:

struct Test {
   int data;
};

Then the array:

然后数组:

struct Test **array1;

Is this correct? My issue is working with this thing. So each pointer in the array points to something that is allocated separately. But I think I need to do this first:

这样对吗?我的问题是处理这件事。所以数组中的每个指针都指向单独分配的东西。但我想我需要先做到这一点:

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

I am having trouble understanding the above. Do I need to do this, and why do I need to do this? In particular, what does it mean to allocate memory for pointers if I am going to be allocating memory for each thing that the pointer points to?

我无法理解上述内容。我是否需要这样做,为什么我需要这样做?特别是,如果我要为指针指向的每个事物分配内存,那么为指针分配内存意味着什么?

Say now I have pointer to an array of pointers to structures. I now want it to point to the same array that I've created earlier.

现在说我有一个指向结构指针数组的指针。我现在希望它指向我之前创建的同一个数组。

struct Test **array2;

Do I need to allocate room for pointers like I did above, or can I just do:

我是否需要像上面那样为指针分配空间,或者我可以这样做:

array2 = array1

回答by teppic

Allocated Array

分配数组

With an allocated array it's straightforward enough to follow.

使用分配的数组,它很容易遵循。

Declare your array of pointers. Each element in this array points to a struct Test:

声明你的指针数组。此数组中的每个元素都指向一个struct Test

struct Test *array[50];

Then allocate and assign the pointers to the structures however you want. Using a loop would be simple:

然后根据需要分配和分配指向结构的指针。使用循环很简单:

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

Then declare a pointer to this array:

然后声明一个指向这个数组的指针:

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

This allows you to use (*p)[n]->data; to reference the nth member.

这允许您使用(*p)[n]->data; 引用第 n 个成员。

Don't worry if this stuff is confusing. It's probably the most difficult aspect of C.

如果这些东西令人困惑,请不要担心。这可能是 C 最困难的方面。



Dynamic Linear Array

动态线阵

If you just want to allocate a block of structs (effectively an array of structs, notpointers to structs), and have a pointer to the block, you can do it more easily:

如果您只想分配一个结构块(实际上是一个结构数组,而不是指向结构的指针),并且有一个指向该块的指针,则可以更轻松地完成:

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

You can then point to this pointer:

然后你可以指向这个指针:

struct Test **pp = &p

You don't have an array of pointers to structs any more, but it simplifies the whole thing considerably.

您不再有指向结构的指针数组,但它大大简化了整个过程。



Dynamic Array of Dynamically Allocated Structs

动态分配结构的动态数组

The most flexible, but not often needed. It's very similar to the first example, but requires an extra allocation. I've written a complete program to demonstrate this that should compile fine.

最灵活,但不经常需要。它与第一个示例非常相似,但需要额外的分配。我已经编写了一个完整的程序来演示这应该可以正常编译。

#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;
}

Output:

输出:

> 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

Or the whole set:

或者全套:

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 


Dynamic Pointer Array of Single-Dynamic Allocated Structs

单动态分配结构的动态指针数组

This last example is rather specific. It is a dynamic array of pointers as we've seen in previous examples, but unlike those, the elements are all allocated in a singleallocation. This has its uses, most notable for sorting data in different configurations while leaving the original allocation undisturbed.

最后一个例子相当具体。正如我们在前面的示例中看到的那样,它是一个动态指针数组,但与那些不同的是,所有元素都在单个分配中分配。这有其用途,最显着的是对不同配置中的数据进行排序,同时保持原始分配不受干扰。

We start by allocating a single block of elements as we do in the most basic single-block allocation:

我们首先分配一个元素块,就像我们在最基本的单块分配中所做的那样:

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

Now we allocate a separateblock of pointers:

现在我们分配一个单独的指针块:

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

We then populate each slot in our pointer list with the address of one of our original array. Since pointer arithmetic allows us to move from element to element address, this is straight-forward:

然后我们用原始数组之一的地址填充指针列表中的每个插槽。由于指针算法允许我们从一个元素移动到另一个元素地址,这很简单:

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

At this point the following both refer to the same element field

此时以下均指同一个元素字段

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

And after review the above, I hope it is clear why.

在查看上述内容后,我希望您清楚原因

When we're done with the pointer array and the original block array they are freed as:

当我们完成指针数组和原始块数组时,它们被释放为:

free(ptrs);
free(arr);

Note: we do NOT free each item in the ptrs[]array individually. That is not how they were allocated. They were allocated as a single block (pointed to by arr), and that is how they should be freed.

注意:我们不会ptrs[]单独释放数组中的每个项目。他们不是这样分配的。它们被分配为单个块(由 指向arr),这就是它们应该被释放的方式。

So why would someone want to do this? Several reasons.

那么为什么有人想要这样做呢?几个原因。

First, it radically reduces the number of memory allocation calls. Rather then N+1(one for the pointer array, N for individual structures) you now have only two: one for the array block, and one for the pointer array. Memory allocations are one of the most expensive operations a program can request, and where possible, it is desirable to minimize them (note: file IO is another, fyi).

首先,它从根本上减少了内存分配调用的次数。而不是N+1(一个用于指针数组,N 用于单个结构)您现在只有两个:一个用于数组块,一个用于指针数组。内存分配是程序可以请求的最昂贵的操作之一,并且在可能的情况下,最好将它们最小化(注意:文件 IO 是另一个,仅供参考)。

Another reason: Multiple representations of the same base array of data. Suppose you wanted to sort the data both ascending and descending, and have both sorted representations available at the same time. You could duplicate the data array, but that would require a lot of copying and eat significant memory usage. Instead, just allocate an extra pointer array and fill it with addresses from the base array, then sort that pointer array. This has especially significant benefits when the data being sorted is large (perhaps kilobytes, or even larger, per item) The original items remain in their original locations in the base array, but now you have a very efficient mechanism in which you can sort them without having to actually movethem. You sort the array of pointers to items; the items don't get moved at all.

另一个原因:同一基本数据数组的多种表示。假设你想将数据升序和降序排序,并获取这两种排序表示在同一时间。您可以复制数据数组,但这需要大量复制并占用大量内存。相反,只需分配一个额外的指针数组并用基数组中的地址填充它,然后对该指针数组进行排序。当要排序的数据很大(每个项目可能达到千字节,甚至更大)时,这具有特别显着的好处无需实际移动他们。您对指向项目的指针数组进行排序;项目根本不会被移动。

I realize this is an awful lot to take in, but pointer usage is critical to understanding the many powerful things you can do with the C language, so hit the books and keep refreshing your memory. It will come back.

我意识到这是一个非常多的东西,但是指针的使用对于理解你可以用 C 语言做的许多强大的事情是至关重要的,所以点击书本并不断刷新你的记忆。它会回来的。

回答by Adam Liss

It may be better to declare an actual array, as others have suggested, but your question seems to be more about memory management so I'll discuss that.

正如其他人所建议的那样,声明一个实际的数组可能会更好,但是您的问题似乎更多地与内存管理有关,因此我将对此进行讨论。

struct Test **array1;

This is a pointer to the address of a struct Test. (Not a pointer to the struct itself; it's a pointer to a memory location that holds the addressof the struct.) The declaration allocates memory for the pointer, but not for the items it points to. Since an array can be accessed via pointers, you can work with *array1as a pointer to an array whose elements are of type struct Test. But there is not yet an actual array for it to point to.

这是一个指向 a 的地址的指针struct Test。(不是指向结构本身的指针;它是指向保存结构地址的内存位置的指针。)声明为指针分配内存,而不是为它指向的项目分配内存。由于可以通过指针访问数组,因此您可以将其*array1用作指向元素类型为 的数组的指针struct Test。但是还没有一个实际的数组可以指向它。

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

This allocates memory to hold MAXpointers to items of type struct Test. Again, it does notallocate memory for the structs themselves; only for a list of pointers. But now you can treat arrayas a pointer to an allocated array of pointers.

这会分配内存来保存MAX指向类型项的指针struct Test。再次,它并没有分配用于结构本身存储器; 仅用于指针列表。但是现在您可以将其array视为指向已分配指针数组的指针。

In order to use array1, you need to create the actual structs. You can do this by simply declaring each struct with

为了使用array1,您需要创建实际的结构。您可以通过简单地声明每个结构来做到这一点

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

You can also allocate the structs on the heap:

您还可以在堆上分配结构:

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

Once you've allocated memory, you can create a new variable that points to the same list of structs:

分配内存后,您可以创建一个指向相同结构列表的新变量:

struct Test **array2 = array1;

You don't need to allocate any additional memory, because array2points to the same memory you've allocated to array1.

您不需要分配任何额外的内存,因为array2指向您分配给的相同内存array1



Sometimes you wantto have a pointer to a list of pointers, but unless you're doing something fancy, you may be able to use

有时您希望有一个指向指针列表的指针,但除非您正在做一些奇特的事情,否则您可以使用

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

This declares the pointer array1, allocated enough memory for MAXstructures, and points array1to that memory. Now you can access the structs like this:

这声明了指针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.


So what's the difference? A few things. Clearly, the first method requires you to allocate memory for the pointers, and then allocate additional space for the structs themselves; the second lets you get away with one malloc()call. What does the extra work buy you?

那么有什么区别呢?一些东西。显然,第一种方法要求您为指针分配内存,然后为结构本身分配额外的空间;第二个让您只需一个malloc()电话就可以逃脱。额外的工作给你带来了什么?

Since the first method gives you an actual array of pointers to Teststructs, each pointer can point to any Teststruct, anywhere in memory; they needn't be contiguous. Moreover, you can allocate and free the memory for each actual Teststruct as necessary, and you can reassign the pointers. So, for example, you can swap two structures by simply exchanging their pointers:

由于第一种方法为您提供了指向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.

On the other hand, the second method allocates a single contiguous block of memory for all of the Teststructs and partitions it into MAXitems. And each element in the array resides at a fixed position; the only way to swap two structures is to copy them.

另一方面,第二种方法为所有Test结构分配一个连续的内存块并将其划分为多个MAX项目。并且数组中的每个元素都驻留在固定位置;交换两个结构的唯一方法是复制它们。

Pointers are one of the most useful constructs in C, but they can also be among the most difficult to understand. If you plan to continue using C, it'll probably be a worthwhile investment to spend some time playing with pointers, arrays, and a debugger until you're comfortable with them.

指针是 C 中最有用的结构之一,但它们也可能是最难理解的结构之一。如果您打算继续使用 C,那么花一些时间使用指针、数组和调试器直到您对它们感到满意为止可能是一项值得的投资。

Good luck!

祝你好运!

回答by wildplasser

Structs are not very different from other objects. Let's start with characters:

结构体与其他对象没有太大区别。让我们从字符开始:

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

*p is a character, so sizeof *pis sizeof (char) == 1; we allocated CNT characters. Next:

*p 是一个字符,所以sizeof *psizeof (char) == 1; 我们分配了 CNT 个字符。下一个:

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

*p is a pointer to character, so sizeof *ppis sizeof (char*). We allocated CNT pointers. Next:

*p 是指向字符的指针,sizeof *ppsizeof (char*) 也是如此。我们分配了 CNT 指针。下一个:

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

*p is a struct something, so sizeof *pis sizeof (struct something). We allocated CNT struct somethings. Next:

*p 是一个结构体,sizeof *psizeof(结构体)也是如此。我们分配了 CNT 结构的东西。下一个:

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

*pp is a pointer to struct, so sizeof *ppis sizeof (struct something*). We allocated CNT pointers.

*pp 是指向 struct 的指针,sizeof *ppsizeof (struct something*) 也是如此。我们分配了 CNT 指针。

回答by Richard Chambers

I suggest that you build this out a layer at a time using typdefs to create layers of types. By doing so, the different types needed will be much clearer.

我建议您使用 typdefs 一次构建一个层来创建类型层。通过这样做,所需的不同类型将更加清晰。

For instance:

例如:

typedef struct Test {
   int data;
} TestType;

typedef  TestType * PTestType;

This will create two new types, one for the struct and one for a pointer to the struct.

这将创建两种新类型,一种用于结构,另一种用于指向结构的指针。

So next if you want an array of the structs then you would use:

所以接下来如果你想要一个结构数组,那么你可以使用:

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

If you want an array of pointers to the structs then you would use:

如果你想要一个指向结构的指针数组,那么你可以使用:

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

Then if you want to allocate structs into the array you would do something like:

然后,如果您想将结构分配到数组中,您可以执行以下操作:

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 does not allow you to assign one array to another. You must instead use a loop to assign each element of one array to an element of the other.

C 不允许您将一个数组分配给另一个数组。您必须改为使用循环将一个数组的每个元素分配给另一个数组的元素。

EDIT: Another Interesting Approach

编辑:另一种有趣的方法

Another approach would be a more object oriented approach in which you encapsulate a few things. For instance using the same layers of types we create two types:

另一种方法是更加面向对象的方法,您可以在其中封装一些东西。例如,使用相同的类型层,我们创建了两种类型:

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;

Next we have a helper function which we use to create the object, named appropriately enough CreateTestData (int nArrayCount).

接下来,我们有一个辅助函数,我们用它来创建对象,命名适当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;
}

Now we can use our new object as in the source code segment below. It should check that the pointer returned from CreateTestData() is valid however this is really just to show what could be done.

现在我们可以在下面的源代码段中使用我们的新对象。它应该检查从 CreateTestData() 返回的指针是否有效,但这实际上只是为了显示可以做什么。

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

In a truly dynamic environment you may also want to have a ReallocTestData(PTestData p)function that would reallocate a TestDataobject in order to modify the size of the array contained in the object.

在真正动态的环境中,您可能还需要一个ReallocTestData(PTestData p)函数来重新分配TestData对象,以修改对象中包含的数组的大小。

With this approach, when you are done with a particular TestData object, you can just free the object as in free (go)and the object and its array are both freed at the same time.

使用这种方法,当您完成特定的 TestData 对象时,您可以像 in 一样释放该对象,free (go)并且同时释放该对象及其数组。

Edit: Extending Further

编辑:进一步扩展

With this encapsulated type we can now do a few other interesting things. For instance, we can have a copy function, PTestType CreateCopyTestData (PTestType pSrc)which would create a new instance and then copy the argument to a new object. In the following example, we reuse the function PTestType CreateTestData (int nCount)that will create an instance of our type, using the size of the object we are copying. After doing the create of the new object, we make a copy of the data from the source object. The final step is to fix up the pointer which in the source object points to its data area so that pointer in the new object now points to the data area of itself rather than the data area of the old object.

使用这种封装类型,我们现在可以做一些其他有趣的事情。例如,我们可以有一个复制函数,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;
}