英文:
Is my understanding of CS50 Lab 5: Inheritance correct?
问题
你的问题中有一些需要解释的点,我会分别回答它们:
-
在代码中,使用
malloc
函数为person
结构体分配内存空间时,分配的内存大小由sizeof(person)
确定。这将分配足够的内存以容纳person
结构体的两个指针(用于父母)和两个char
型变量(用于等位基因)。计算机能够知道要分配多少内存,因为sizeof(person)
返回person
结构体的字节大小。 -
关于递归调用的问题,每次递归调用
create_family
函数时,都会创建一个新的person
结构体实例,但每个实例都有自己的内存分配。在代码中,使用malloc
为每个实例分配不同的内存,因此不会导致之前分配的内存被覆盖或丢失。每个person
结构体都有独立的内存块,而且在递归调用结束时,它们都正确地链接到它们的父代。 -
在
free_family
函数中,确实会递归地释放所有祖先的内存。这是因为在递归调用中,首先会释放每个祖先的父代,然后才会释放该祖先自己的内存。这确保了正确的内存释放顺序,以防止内存泄漏。
总结:你的理解大体上是正确的。计算机会为每个person
结构体分配足够的内存空间,递归调用不会导致内存丢失,而free_family
函数会递归释放所有祖先的内存。希望这些解释有助于澄清你的疑虑。
英文:
So I'd like if someone could tell me if my understanding of the Inheritance problem from Week 5 is correct or not.
// Simulate genetic inheritance of blood type
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// Each person has two parents and two alleles
typedef struct person
{
struct person *parents[2];
char alleles[2];
}
person;
const int GENERATIONS = 3;
const int INDENT_LENGTH = 4;
person *create_family(int generations);
void print_family(person *p, int generation);
void free_family(person *p);
char random_allele();
int main(void)
{
// Seed random number generator
srand(time(0));
// Create a new family with three generations
person *p = create_family(GENERATIONS);
// Print family tree of blood types
print_family(p, 0);
// Free memory
free_family(p);
}
// Create a new individual with `generations`
person *create_family(int generations)
{
// TODO: Allocate memory for new person
person *p = malloc(sizeof(person));
// If there are still generations left to create
if (generations > 1)
{
// Create two new parents for current person by recursively calling create_family
person *parent0 = create_family(generations - 1);
person *parent1 = create_family(generations - 1);
// TODO: Set parent pointers for current person
p->parents[0] = parent0;
p->parents[1] = parent1;
// TODO: Randomly assign current person's alleles based on the alleles of their parents
p->alleles[0] = parent0->alleles[rand() % 2];
p->alleles[1] = parent1->alleles[rand() % 2];
}
// If there are no generations left to create
else
{
// TODO: Set parent pointers to NULL
p->parents[0] = NULL;
p->parents[1] = NULL;
// TODO: Randomly assign alleles
p->alleles[0] = random_allele();
p->alleles[1] = random_allele();
}
// TODO: Return newly created person
return p;
}
// Free `p` and all ancestors of `p`.
void free_family(person *p)
{
// TODO: Handle base case
if (p == NULL)
{
return;
}
// TODO: Free parents recursively
free_family(p->parents[0]);
free_family(p->parents[1]);
// TODO: Free child
free(p);
}
// Print each family member and their alleles.
void print_family(person *p, int generation)
{
// Handle base case
if (p == NULL)
{
return;
}
// Print indentation
for (int i = 0; i < generation * INDENT_LENGTH; i++)
{
printf(" ");
}
// Print person
if (generation == 0)
{
printf("Child (Generation %i): blood type %c%c\n", generation, p->alleles[0], p->alleles[1]);
}
else if (generation == 1)
{
printf("Parent (Generation %i): blood type %c%c\n", generation, p->alleles[0], p->alleles[1]);
}
else
{
for (int i = 0; i < generation - 2; i++)
{
printf("Great-");
}
printf("Grandparent (Generation %i): blood type %c%c\n", generation, p->alleles[0], p->alleles[1]);
}
// Print parents of current generation
print_family(p->parents[0], generation + 1);
print_family(p->parents[1], generation + 1);
}
// Randomly chooses a blood type allele.
char random_allele()
{
int r = rand() % 3;
if (r == 0)
{
return 'A';
}
else if (r == 1)
{
return 'B';
}
else
{
return 'O';
}
}
So we start by allocating space for the individual. My question here is...how much space does the computer allocate? The person structure has 2 parent persons, which will have their own parents and so on and so forth. So how does the computer know how much space to allocate?
After allocating space for the person, we start recursively calling the function for each generation. So let's name them as the individual, P[A], P[B], GP[A] and GP[B], where P -> Parent, and GP -> Grandparent and A and B mean first and second respectively.
So we first go into the loop for P[A]. We ask the computer to allocate space again. But we use the same variable p to point to a new block of memory for P[A]. Won't that result in the block of memory we allocated for the individual being lost? Then we call the function for the grandparents of P[A], i.e. GP[A] and GP[B]. We first go into GP[A], and since generation is < 1, we set its parent pointers to null and give it random alleles. Same for GP[B].
After both calls of the function are done for P[A], we then set the parents array of P[A] to be equal to the pointers, parent0 and parent1, which point to GP[A] and GP[B] respectively.
Then we do the same for P[B].
Then we do the same for the individual and we move on to the next function, free_family.
In free_family, do we end up calling the function for the parents of each grandparent as well? Cuz then it makes sense that the function returns and we end up freeing the child, which in this case is the grandparent. And then we do the same for each grandparent, which results in the parents being freed. And then finally we free the individual.
So is my explanation correct. Do you have any explanations for my questions that I have highlighted in bold? Would greatly appreciate any advice.
答案1
得分: 2
在分配个体空间时,计算机分配多少空间?struct person
结构具有2个父母 persons
,而这些父母将有他们自己的父母,以此类推。计算机如何知道要分配多少空间?
你在混淆数据类型的语义解释与它们的实际结构,也可能对间接引用感到困惑。像每个 C 结构一样(不具有灵活数组成员的结构),struct person
有一个固定的、明确的大小。你可以使用 sizeof
运算符来确定这个大小。它至少要足够大,以容纳所有成员,这些成员包括:
- 两个指向
struct person
的指针数组 - 两个
char
数组
在典型的 64 位指针实现中,我期望这样的结构大小为 24 字节,其中 16 字节用于两个指针,两个用于两个 char
,还有 6 字节的填充以满足对齐要求。不同的实现可能会有不同的选择,但不管大小是多少,malloc(sizeof(person))
将尝试分配这么多字节。
请注意,一个 struct person
不包含其他 struct person
。C 不允许一个结构类型直接或间接地包含其自身类型的子对象。但它允许结构包含指向相同类型的其他对象的指针,这就是你的情况。
在程序中,我看不到任何这样的循环。除非这个问题是假设性的(在这种情况下答案取决于未提供的许多细节),否则我认为你可能在谈论 create_family
函数,它不会循环,但会递归调用。
在这里,你需要理解函数的一个关键属性是每个函数执行都有其自己的一组非static
局部变量,包括递归调用时也是如此。因此,初始调用 create_family
中的 p
是一个不同的变量,与每个递归调用 create_family
中使用的 p
不同,每个递归调用都有它们自己的 p
,以此类推。这些都是独立的,一个不会覆盖另一个。
在 free_family
中,我们最终是否会调用每个祖父母的函数?因此,函数返回并释放孩子,这在这种情况下是祖父母。然后我们对每个祖父母执行相同的操作,导致父母被释放。最后,我们释放个体。
是的。free_family
通过递归调用自身来释放每个人的父母。因此,在这些递归调用中,它释放了父母的父母,以此类推,一直到在每个分支中标记该人的父母未记录的点(由 null 指针表示)。
英文:
> So we start by allocating space for the individual. My question here is...how much space does the computer allocate? The person structure has 2 parent persons, which will have their own parents and so on and so forth. So how does the computer know how much space to allocate?
You are mixing up the semantic interpretation of your data types with their actual structure, and perhaps also getting confused about indirection. Like every C struct (that does not have a flexible array member), your struct person
has a fixed, definite size. You can, for example, determine this size via the sizeof
operator. It will be at least large enough to accommodate all the members, which are
- an array of two pointers to
struct person
- an array of two
char
On a typical implementation featuring 64-bit pointers, I would expect such a structure to be 24 bytes in size -- 16 for the two pointers, two for the two char
s, and 6 bytes of padding for alignment purposes. Implementations may choose differently, but whatever that size is, that's how many bytes malloc(sizeof(person))
will attempt to allocate.
And note well that one struct person
does not contain others. C does not permit a structure type to directly or indirectly contain sub-objects of its own type. But it does permit structures to contain pointers to other objects of the same type, which is what you have.
> So we first go into the loop for P[A]. We ask the computer to allocate space again. But we use the same variable p to point to a new block of memory for P[A]. Won't that result in the block of memory we allocated for the individual being lost? Then we call the function for the grandparents of P[A], i.e. GP[A] and GP[B]. We first go into GP[A], and since generation is < 1, we set its parent pointers to null and give it random alleles. Same for GP[B].
I don't see any such loop in the program. Unless the question is hypothetical (in which case the answer depends on many details that have not been presented), I think you must be talking about the create_family
function, which does not loop, but does recurse.
Here you need to understand that among the key properties of functions is that each execution of any function gets its own set of non-static
local variables. Including when it calls itself recursively. So the p
in the initial call to create_family
is a different variable from the p
used in each of its recursive calls to create_family
, and each of those calls' own recursive calls get their own p
s, etc.. These are all independent; one does not clobber another.
> In free_family, do we end up calling the function for the parents of each grandparent as well? Cuz then it makes sense that the function returns and we end up freeing the child, which in this case is the grandparent. And then we do the same for each grandparent, which results in the parents being freed. And then finally we free the individual.
Yes. free_family
frees the parents of each person by recursively calling itself. So in those recursive calls, it frees the parents' parents. And in those calls, the parents' parents' parents, up to the point in each branch where the person's parents are not recorded (as indicated by null pointers).
答案2
得分: 1
以下是翻译好的部分:
So I'd like if someone could tell me if my understanding of the Inheritance problem from Week 5 is correct or not.
这个问题的名字有点令人困惑,因为继承是一个独立的计算机科学概念,但在C中并不适用。你真正学到的是递归函数背后的概念,尽管我没有进行调试/执行/编译,但从概念上来说,你提出了正确的问题。
how does the computer know how much space to allocate?
由于你递归地调用了
create_family
,编译器不需要事先计算需要分配多少空间。它只需按顺序运行每个malloc
,就会分配正确数量的空间。计算机在分配时会一边进行,不需要在任何时候知道总共需要多少空间。
we use the same variable p
变量
p
的作用范围限定在create_family
的特定调用中。每次调用create_family
时,它都会获得自己的p
。
In free_family, do we end up calling the function for the parents of each grandparent as well?
free_family
也是递归的。它会对每个父代调用free_family
。但然后对于每个父代,它会对父代的父代调用free_family
。对于每个祖父代,它会对这些父代中的每一个都调用free_family
。所以是的,使用递归的create_family
分配的每一代在调用free_family
时也会被释放。
英文:
> So I'd like if someone could tell me if my understanding of the Inheritance problem from Week 5 is correct or not.
It's a confusing name since Inheritance is a separate CS concept, but doesn't apply to C anyway. What you're really learning here is the concepts behind recursive functions, and while I didn't debug / execute / compile, conceptually you are asking the correct questions.
> how does the computer know how much space to allocate?
Since you call create_family
recursively, the compiler doesn't have to compute how much space beforehand. It can simply run each malloc
in order and the correct amount of space is allocated. The computer just allocates as it goes, it doesn't need to know how much total at any time.
> we use the same variable p
Variable p
is scoped to a particular invocation of create_family
. Each time you call create_family
it gets its own p
.
> In free_family, do we end up calling the function for the parents of each grandparent as well?
free_family
is also recursive. It calls free_family
on each parent. But then for each parent, it calls free_family
for each of the parent's parents. And for each grandparent, it calls free_family
for each of those parents. So yes, every generation that was allocated with the recursive create_family
will also be deallocated when you call free_family
.
答案3
得分: 0
以下是您要翻译的内容:
"不回答你的问题,只是作为一个附注:解决这个问题的一个更简单的方法是维基百科中提供的示例。一个(简化的)家谱图是完美二叉树的一个示例。代数k导致固定数量的人(节点),n = 2^(k+1) - 1。在树的术语中,父节点有两个子节点;在家谱树中,情况正好相反:两个父节点产生一个子节点。
这使我们能够将数据结构存储在固定的紧凑数组中,作为堆上的自动变量,使用索引i,2i+1和2i+2作为父节点(在树的术语中是子节点),以及(i-1)/2作为子节点(在树的术语中是父节点)。我们在数组中向后工作。没有以前家谱记录的顶级祖先数量对应于叶节点,其中有l = (n + 1)/2个。
#include <stdlib.h> /* Includes stddef. */
#include <stdio.h>
#include <time.h>
#define GENERATIONS 3
#define NODES ((2 << (GENERATIONS + 1)) - 1)
#define LEAVES ((NODES + 1) / 2)
/* X-Macro allows parallel assignment of enum and strings with one source. */
#define ALLELES X(A), X(B), X(O)
#define X(a) a
enum allele { ALLELES };
#undef X
#define X(a) #a
static const char *const alleles[] = { ALLELES };
#undef X
#undef ALLELES
static const size_t alleles_size = sizeof alleles / sizeof *alleles;
/** Two enums is overkill for only 9 combinations, but convenient. */
struct person { enum allele allele[2]; };
int main(void) {
/* An (idealized) family tree is a perfect binary tree
<https://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees>. */
struct person family[NODES];
size_t i;
srand((unsigned)clock()); /* Seed. */
printf("digraph {\n"
"\tgraph [truecolor=true, bgcolor=transparent, fontname=modern];\n"
"\tnode [shape=none, fontname=modern];\n"
"\n"
"\t// leaves; ancestor nodes\n");
for(i = LEAVES - 1; i < NODES; i++) {
struct person *const leaf = family + i;
leaf->allele[0] = (unsigned)rand() / (RAND_MAX / alleles_size + 1);
leaf->allele[1] = (unsigned)rand() / (RAND_MAX / alleles_size + 1);
printf("\tnode%zu [label = \"%s%s\"];\n",
i, alleles[leaf->allele[0]], alleles[leaf->allele[1]]);
}
printf("\n"
"\t// branches; backwards\n");
i = LEAVES - 1; do {
/* https://en.wikipedia.org/wiki/Binary_tree#Arrays */
const size_t p = 2 * --i + 1;
const struct person *const parents = family + p;
struct person *const branch = family + i;
branch->allele[0] = parents[0].allele[rand() > RAND_MAX / 2];
branch->allele[1] = parents[1].allele[rand() > RAND_MAX / 2];
printf("\tnode%zu [label = \"%s%s\"];\n"
"\tnode%zu -> node%zu;\n"
"\tnode%zu -> node%zu;\n",
i, alleles[branch->allele[0]], alleles[branch->allele[1]],
p, i, p + 1, i);
} while(i);
printf("}\n");
return EXIT_SUCCESS;
}
通过GraphViz管道传递的示例输出:
"
请注意,我已经将原始内容中的HTML转义字符进行了修正,以使其更易于阅读。
英文:
Not to answer your questions, but as a side note: a simpler way to solve this is the example given by Wikipedia. A (simplified) ancestry chart is an example of a perfect binary tree. The generations, k
, leads to a fixed number of persons (nodes), n = 2^(k+1) - 1
. In tree terminology, the parent has two children; in a genealogy tree, it is the other way around: two parents produce a child.
This allows us to store the data structure in a fixed packed array as an automatic variable on the heap using index i
, 2i+1
and 2i+2
as parents (in tree terminology, children) and (i-1)/2
as the child (in tree terminology, parent). We work backwards in the array. The number of top ancestors without record of previous ancestry corresponds to the leaf nodes, of which there are l = (n + 1)/2
.
#include <stdlib.h> /* Includes stddef. */
#include <stdio.h>
#include <time.h>
#define GENERATIONS 3
#define NODES ((2 << (GENERATIONS + 1)) - 1)
#define LEAVES ((NODES + 1) / 2)
/* X-Macro allows parallel assignment of enum and strings with one source. */
#define ALLELES X(A), X(B), X(O)
#define X(a) a
enum allele { ALLELES };
#undef X
#define X(a) #a
static const char *const alleles[] = { ALLELES };
#undef X
#undef ALLELES
static const size_t alleles_size = sizeof alleles / sizeof *alleles;
/** Two enums is overkill for only 9 combinations, but convenient. */
struct person { enum allele allele[2]; };
int main(void) {
/* An (idealized) family tree is a perfect binary tree
<https://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees>. */
struct person family[NODES];
size_t i;
srand((unsigned)clock()); /* Seed. */
printf("digraph {\n"
"\tgraph [truecolor=true, bgcolor=transparent, fontname=modern];\n"
"\tnode [shape=none, fontname=modern];\n"
"\n"
"\t// leaves; ancestor nodes\n");
for(i = LEAVES - 1; i < NODES; i++) {
struct person *const leaf = family + i;
leaf->allele[0] = (unsigned)rand() / (RAND_MAX / alleles_size + 1);
leaf->allele[1] = (unsigned)rand() / (RAND_MAX / alleles_size + 1);
printf("\tnode%zu [label = \"%s%s\"];\n",
i, alleles[leaf->allele[0]], alleles[leaf->allele[1]]);
}
printf("\n"
"\t// branches; backwards\n");
i = LEAVES - 1; do {
/* https://en.wikipedia.org/wiki/Binary_tree#Arrays */
const size_t p = 2 * --i + 1;
const struct person *const parents = family + p;
struct person *const branch = family + i;
branch->allele[0] = parents[0].allele[rand() > RAND_MAX / 2];
branch->allele[1] = parents[1].allele[rand() > RAND_MAX / 2];
printf("\tnode%zu [label = \"%s%s\"];\n"
"\tnode%zu -> node%zu;\n"
"\tnode%zu -> node%zu;\n",
i, alleles[branch->allele[0]], alleles[branch->allele[1]],
p, i, p + 1, i);
} while(i);
printf("}\n");
return EXIT_SUCCESS;
}
Sample output piped though GraphViz:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论