指向结构体的动态指针数组

6
我必须在学校作业中使用以下代码块,严格禁止任何修改。
typedef struct 
{
    char* firstName;
    char* lastName;
    int id;
    float mark;
}* pStudentRecord;

pStudentRecord* g_ppRecords;
int g_numRecords =0;

在这里,g_ppRecords应该是一个指向结构体的指针数组。我完全不明白的是,如何理解语句pStudentRecords *g_ppRecords;中的g_ppRecords表示数组,因为数组应该定义为

type arrayname[size];

我尝试动态地为g_ppRecords分配内存,但这并没有帮助解决问题。

g_ppRecords = (pStudentRecord*) malloc(sizeof(pStudentRecord*)*(g_numRecords+1));

一个指针只是指向一个地址。你可以从那个地址开始保留尽可能多的内存(直到空间用完为止)。 - chris
我认为这是一个不好的问题,你应该明白数组可以被定义成这样,因为你试图为它分配空间,那么你在问什么呢?仅仅为指向结构体的指针(pStudentRecord 而不是 pSt...ord *)分配空间是不够的,为了使用它,你还需要为结构体本身分配空间! - Michael
4个回答

3

编辑:更新了“大错误”部分。

关于C语言(不同于C++!)typedef的快速教程,以及它为什么是这样,如何使用它。

首先,一个基本的typedef技巧。

typedef int* int_pointer;
int_pointer ip1;
int *ip2;
int a;    // Just a variable
ip1 = &a; // Sets the pointer to a
ip2 = &a; // Sets the pointer to a
*ip1 = 4; // Sets a to 4
*ip2 = 4; // Sets a to 4

ip1和ip2是相同类型:指向int类型的指针,即使在ip1的声明中没有放置*。那个*实际上是在声明中放置的。

话题转换。 你说声明数组应该如何:

int array1[4];

为了在运行时动态地实现这一点,您可以进行以下操作:
int *array2 = malloc(sizeof(int) * 4);
int a = 4;
array1[0] = a;
array2[0] = a; // The [] implicitly dereferences the pointer

现在,如果我们想要一个指针数组?它看起来会像这样:

[代码]

int *array1[4];
int a;
array1[0] = &a; // Sets array[0] to point to variable a
*array1[0] = 4; // Sets a to 4

让我们动态地分配那个数组。

int **array2 = malloc(sizeof(int *) * 4);
array2[0] = &a; // [] implicitly dereferences
*array2[0] = 4; // Sets a to 4

注意 int ** 的含义,这意味着指向指针的指针。如果我们愿意,可以使用指针 typedef。
typedef int* array_of_ints;
array_of_ints *array3 = malloc(sizeof(array_of_ints) * 4);
array3[0] = &a; // [] implicitly dereferences
*array3[0] = 4; // Sets a to 4

注意在最后一个声明中只有一个*号?这是因为其中一个星号“在typedef中”。通过这个声明,你现在有一个大小为4的数组,它由4个指向整数(int *)的指针组成。

这里需要指出运算符优先级。解引用操作符[]优先于*操作符。所以为了绝对清楚,我们正在做以下操作:

*(array3[0]) = 4;

现在,我们来谈谈结构体和类型定义。

struct foo { int a; }; // Declares a struct named foo
typedef struct { int a; } bar; // Typedefs an "ANONYMOUS STRUCTURE" referred to by 'bar'

为什么您要给匿名结构体取个typedef呢?好吧,为了可读性!

struct foo a; // Declares a variable a of type struct foo
bar b;        // Notice how you don't have to put 'struct' first

声明一个函数...

funca(struct foo* arg1, bar *arg2);

看到了吗,我们不需要在arg2前面加上“struct”?

现在,我们需要使用以下方式定义结构体的代码:

typedef struct { } * foo_pointers;

这类似于我们以前处理指针数组的方式:

typedef int* array_of_ints;

并排比较

typedef struct { } * foo_pointers;
typedef int* array_of_ints;

唯一的区别在于一个是针对struct {},另一个是针对int。
使用我们的foo_pointers,我们可以声明一个指向foo的指针数组,如下所示:
foo_pointers fooptrs[4];

现在我们有一个数组,存储了4个指向无法访问的匿名结构体的指针。

话题转换!

不幸的是,你的老师犯了一个错误。如果查看上面foo_pointers类型的sizeof(),会发现它返回的是指向该结构体的指针的大小,而不是结构体本身的大小。对于32位平台,这是4个字节,对于64位平台,这是8个字节。这是因为我们typedef了一个指向结构体的指针,而不是结构体本身。sizeof(pStudentRecord)将返回4。

因此,你不能以明显的方式为结构体本身分配空间!然而,编译器允许这种愚蠢的行为。pStudentRecord不是您可以用来有效分配内存的名称/类型,它是一个指向匿名"概念性"结构体的指针,但我们可以将其大小提供给编译器。

pStudnetRecord g_ppRecords[2]; pStudentRecord *record = malloc(sizeof(*g_ppRecords[1]));

更好的做法是这样做:

typedef struct { ... } StudentRecord;  // Struct
typedef StudentRecord* pStudentRecord; // Pointer-to struct

我们现在可以清晰地制作struct StudentRecord,以及指向它们的pStudentRecord指针。
虽然强制使用的方法非常糟糕,但目前不是问题。让我们回到使用int的简化示例。
如果我想要创建一个typedef来使我的生活更加复杂,但解释这里发生的概念呢?让我们回到旧的int代码。
typedef int* array_of_ints;
int *array1[4];
int **array2 = malloc(sizeof(int *) * 4); // Equivalent-ish to the line above
array_of_ints *array3 = malloc(sizeof(array_of_ints) * 4);
int a, b, c, d;
*array1[0] = &a; *array1[1] = &b; *array1[2] = &c; *array1[3] = &d;
*array2[0] = &a; *array2[1] = &b; *array2[2] = &c; *array2[3] = &d;
*array3[0] = &a; *array3[1] = &b; *array3[2] = &c; *array3[3] = &d;

正如您所看到的,我们可以将其与我们的pStudentRecord一起使用:

pStudentRecord array1[4];
pStudentRecord *array2 = malloc(sizeof(pStudentRecord) * 4);

将所有内容汇总,逻辑上可以得出:

array1[0]->firstName = "Christopher";
*array2[0]->firstName = "Christopher";

这两者是等价的。(注意:不要像我上面那样精确地分配char*指针到一个字符串,除非你知道已经分配了足够的空间)。

这仅仅带来了最后一点问题。我们如何处理我们malloc的所有内存?我们怎样释放它?

free(array1);
free(array2);

在深夜指针、匿名结构体的typedef以及其他相关内容的学习结束了。

2

请注意,pStudentRecord 被定义为指向结构体的指针。在 C 语言中,指针只是指向一块内存区域的起始位置,无论这块区域是包含 1 个元素(标准的“标量”指针)还是 10 个元素(“数组”指针)。因此,例如下面的代码:

char c = 'x';
char *pc = &c;

使pc指向以字符'x'开头的一块内存。而下面的内容是:
char *s = "abcd";

使s指向一块以"abcd"开头(后跟一个空字节)的内存片段,类型相同,但可能被用于不同的目的。
因此,一旦分配,我可以通过g_ppRecords[1]->firstName这样的方式访问g_ppRecords中的元素。
现在,要分配这个数组:您需要使用g_ppRecords = malloc(sizeof(pStudentRecord)*(g_numRecords+1));(注意,sizeof(pStudentRecord*)sizeof(pStudentRecord)是相等的,因为两者都是指针类型)。这将创建一个未初始化的结构体指针数组。对于数组中的每个结构体指针,您需要通过分配新结构体来给它一个值。问题的关键在于如何分配单个结构体,也就是说,
g_ppRecords[1] = malloc(/* what goes here? */);

幸运的是,在sizeof中您实际上可以取消引用指针:
g_ppRecords[1] = malloc(sizeof(*g_ppRecords[1]));

请注意,sizeof 是编译器的构造。即使 g_ppRecords [1] 不是有效指针,类型 仍然有效,因此编译器将计算出正确的大小。

g_ppRecords[1] = (pStudentRecord*) malloc(sizeof(char*) * 2 + sizeof(int) + sizeof(float)); - Moez Hirani
添加了一个更好的解决方案。想想看,它实际上并不是一种显而易见的解决方案。 - nneonneo
在纯C中,将malloc()的调用强制转换是不好的实践,甚至会得到警告。这是一个不好的C++习惯。 - std''OrgnlDave

0

数组通常用指向其第一个元素的指针来引用。如果你为10个学生记录分配了足够的空间,然后将指向该空间开头的指针存储在g_ppRecords中,g_ppRecords[9]将向前计算9个记录指针长度并解除引用那里的内容。如果你正确地管理了空间,那里的内容将是你数组中的最后一条记录,因为你为10个记录保留了足够的空间。

简而言之,你已经分配了空间,如果长度正确,你可以按任何方式处理它,包括作为数组。

我不确定为什么你要为g_numRecords + 1个记录分配空间。除非g_numRecords的命名令人困惑,否则这就是为你的数组多分配了一个空间。


-1
这里的g_ppRecords应该是一个指向结构体指针的数组。我完全不理解的是,语句*pStudentRecords g_ppRecords;如何表示g_ppRecords是一个数组,因为数组应该定义为type arrayname[size];。
typedef struct 
{
    char* firstName;
    char* lastName;
    int id;
    float mark;
}*  pStudentRecord;

pStudentRecord* g_ppRecords;
int g_numRecords = 0;

这个 typedef 与大多数不同,请注意 }*,基本上它是一个指向结构体的指针,因此:

pStudentRecord* g_ppRecords;

实际上是:

struct 
{
    char* firstName;
    char* lastName;
    int id;
    float mark;
}** pStudentRecord;

这是一个指向指针的typedef,为什么他们要以这种方式定义它,我无法理解,个人不建议使用,为什么呢?

问题之一是,我们如何通过结构体的名称获取其大小?简单来说,我们无法做到!如果我们使用sizeof(pStudentRecord),我们将得到48,取决于底层架构,因为它是一个指针,而不知道结构体的大小,我们无法使用typedef名称动态分配它,那么我们该怎么办呢?声明一个第二个结构体,如下:

typedef struct 
{
    char* firstName;
    char* lastName;
    int id;
    float mark;
} StudentRecord;

g_ppRecords = malloc(sizeof(StudentRecord) * g_numRecords);

无论如何,你真的需要联系原始创建这段代码的人或者负责维护的人,并提出你的疑虑。
g_ppRecords=(pStudentRecord) malloc( (sizeof(char*) + 
                                  sizeof(char*) + 
                                  sizeof(int)   + 
                                  sizeof(float)) *(g_numRecords+1));

这似乎是一种可能的方式,但不幸的是,结构体没有保证,因此它们实际上可以在成员之间包含填充,因此结构体的总大小实际上可能比其组合成员更大,更不用说它们的地址可能会有所不同。

编辑

显然,我们可以通过推断其类型来获取结构体的大小

所以:

pStudentRecord g_ppRecords = malloc(sizeof(*g_ppRecords) * g_numRecords);

运行正常!


那是一种非常糟糕的使用malloc的方式,因为你硬编码了结构定义(并忽略了填充等)。你说没有其他方法做到这一点是不正确的;看看我的解决方案。代码如写得很好,可用性也很高,尽管有些不寻常。 - nneonneo
我说了“更差”的方式,无论如何我都会将其删除。 - Samy Vilar
实际上,你回答的关键点,“一个问题是我们如何通过名称获取结构体的大小?简单来说,我们做不到!”是完全错误的。我在我的回答中展示了如何做到这一点。没有必要以任何形式复制结构定义。你提出的所有“我们可以做什么”的答案都是错误的,原因就是我给出的那个。 - nneonneo
而确切地说,我说过“一个问题是我们如何通过结构体的名称获取其大小”,我们并没有使用它的名称,而是使用其类型的变量。无论如何,您已经找到了解决方案,恭喜! - Samy Vilar
1
如果您正在使用GCC并愿意使用非标准扩展,那么相当邪恶的 sizeof({pStudentRecord _; *_;}) 也可以工作,并且您可以将其打包成宏,以便您可以执行 PSIZEOF(pStudentRecord) 或类似操作。这不是推荐的做法。 - nneonneo
+1 是针对“非标准扩展”的,这相当恶劣。是的,我正在使用gcc。 - Samy Vilar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接