C:指向结构指针数组的指针(分配/释放问题)

我已经回到了C的某些东西,但是我很难记住这个内存管理的工作原理。 我想要一个指向结构指针数组的指针。

说我有:

struct Test { int data; }; 

那么数组:

 struct Test **array1; 

它是否正确? 我的问题是处理这件事。 因此,数组中的每个指针都指向单独分配的内容。 但我认为我需要先做到这一点:

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

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

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

 struct Test **array2; 

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

 array2 = array1 

分配的arrays

使用已分配的数组,它可以直接跟随。

声明你的指针数组。 此数组中的每个元素都指向一个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最困难的方面。


动态线性arrays

如果你只想分配一个结构块(实际上是一个结构数组, 而不是指向结构的指针),并且有一个指向该块的指针,你可以更容易地做到:

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

然后,您可以指向此指针:

 struct Test **pp = &p 

你没有一个指向结构的指针数组,但它大大简化了整个过程。


动态分配结构的动态数组

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

 #include  #include  #include  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 

单动态分配结构的动态指针arrays

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

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

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

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

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

然后,我们使用原始数组之一的地址填充指针列表中的每个插槽。 由于指针算法允许我们从元素地址移动到元素地址,因此这是直截了当的:

 for (int i=0;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 *)); 

这将分配内存以保存指向struct Test类型的项的MAX指针。 同样,它不为结构本身分配内存; 仅用于指针列表。 但现在您可以将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 

一旦分配了内存,就可以创建一个指向同一结构列表的新变量:

 struct Test **array2 = array1; 

您不需要分配任何额外的内存,因为array2指向您分配给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项。 并且arrays中的每个元素都位于固定位置; 交换两个结构的唯一方法是复制它们。

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

祝好运!

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

例如:

 typedef struct Test { int data; } TestType; typedef TestType * PTestType; 

这将创建两个新类型,一个用于struct,另一个用于指向struct的指针。

接下来如果您想要一个结构数组,那么您将使用:

 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) ,它将创建一个新实例,然后将参数复制到一个新对象。 在下面的示例中,我们重用函数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是sizeof(char)== 1; 我们分配了CNT字符。 下一个:

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

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

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

* p是一个结构,所以sizeof *p是sizeof(struct something)。 我们分配了CNT结构的东西。 下一个:

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

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