24 / 07 / 12

「一生一芯」Learn C the hard way - Ex 16

练习 16:结构体和指向它们的指针

#include <stdio.h> #include <assert.h> #include <stdlib.h> #include <string.h> struct Person { char *name; int age; int height; int weight; }; struct Person *Person_create(char *name, int age, int height, int weight) { struct Person *who = malloc(sizeof(struct Person)); assert(who != NULL); who->name = strdup(name); who->age = age; who->height = height; who->weight = weight; return who; } void Person_destroy(struct Person *who) { assert(who != NULL); free(who->name); free(who); } void Person_print(struct Person *who) { printf("Name: %s\n", who->name); printf("\tAge: %d\n", who->age); printf("\tHeight: %d\n", who->height); printf("\tWeight: %d\n", who->weight); } int main(int argc, char *argv[]) { // make two people structures struct Person *joe = Person_create( "Joe Alex", 32, 64, 140); struct Person *frank = Person_create( "Frank Blank", 20, 72, 180); // print them out and where they are in memory printf("Joe is at memory location %p:\n", joe); Person_print(joe); printf("Frank is at memory location %p:\n", frank); Person_print(frank); // make everyone age 20 years and print them again joe->age += 20; joe->height -= 2; joe->weight += 40; Person_print(joe); frank->age += 20; frank->weight += 20; Person_print(frank); // destroy them both so we clean up Person_destroy(joe); Person_destroy(frank); return 0; }

试着传递NULLPerson_destroy来看看会发生什么。如果它没有崩溃,你必须移除Makefile的CFLAGS中的-g选项。

在当前的代码中,如果传递 NULLPerson_destroy 函数,在 Person_destroy 函数中使用了 assert(who != NULL); 进行检查。如果 whoNULLassert 会触发断言失败,程序会终止。

ex16: ex16.c:28: Person_destroy: Assertion `who != NULL' failed. Aborted

在结尾处忘记调用Person_destroy,在Valgrind下运行程序,你会看到它报告出你忘记释放内存。弄清楚你应该向valgrind传递什么参数来让它向你报告内存如何泄露。

⮞ valgrind --leak-check=full ./ex16 ==234164== Memcheck, a memory error detector ==234164== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al. ==234164== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info ==234164== Command: ./ex16 ==234164== Joe is at memory location 0x4a95040: Name: Joe Alex Age: 32 Height: 64 Weight: 140 Frank is at memory location 0x4a950f0: Name: Frank Blank Age: 20 Height: 72 Weight: 180 Name: Joe Alex Age: 52 Height: 62 Weight: 180 Name: Frank Blank Age: 40 Height: 72 Weight: 200 ==234164== ==234164== HEAP SUMMARY: ==234164== in use at exit: 69 bytes in 4 blocks ==234164== total heap usage: 5 allocs, 1 frees, 1,093 bytes allocated ==234164== ==234164== 33 (24 direct, 9 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 4 ==234164== at 0x484880F: malloc (vg_replace_malloc.c:446) ==234164== by 0x1091EB: Person_create (ex16.c:15) ==234164== by 0x10936E: main (ex16.c:45) ==234164== ==234164== 36 (24 direct, 12 indirect) bytes in 1 blocks are definitely lost in loss record 4 of 4 ==234164== at 0x484880F: malloc (vg_replace_malloc.c:446) ==234164== by 0x1091EB: Person_create (ex16.c:15) ==234164== by 0x109390: main (ex16.c:48) ==234164== ==234164== LEAK SUMMARY: ==234164== definitely lost: 48 bytes in 2 blocks ==234164== indirectly lost: 21 bytes in 2 blocks ==234164== possibly lost: 0 bytes in 0 blocks ==234164== still reachable: 0 bytes in 0 blocks ==234164== suppressed: 0 bytes in 0 blocks ==234164== ==234164== For lists of detected and suppressed errors, rerun with: -s ==234164== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

忘记在Person_destroy中释放who->name,并且对比两次的输出。同时,使用正确的选项来让Valgrind告诉你哪里错了。

⮞ valgrind --leak-check=full -s ./ex16 ==236601== Memcheck, a memory error detector ==236601== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al. ==236601== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info ==236601== Command: ./ex16 ==236601== Joe is at memory location 0x4a95040: Name: Joe Alex Age: 32 Height: 64 Weight: 140 Frank is at memory location 0x4a950f0: Name: Frank Blank Age: 20 Height: 72 Weight: 180 Name: Joe Alex Age: 52 Height: 62 Weight: 180 Name: Frank Blank Age: 40 Height: 72 Weight: 200 ==236601== ==236601== HEAP SUMMARY: ==236601== in use at exit: 21 bytes in 2 blocks ==236601== total heap usage: 5 allocs, 3 frees, 1,093 bytes allocated ==236601== ==236601== 9 bytes in 1 blocks are definitely lost in loss record 1 of 2 ==236601== at 0x484880F: malloc (vg_replace_malloc.c:446) ==236601== by 0x491158E: strdup (strdup.c:42) ==236601== by 0x10922A: Person_create (ex16.c:18) ==236601== by 0x10935F: main (ex16.c:45) ==236601== ==236601== 12 bytes in 1 blocks are definitely lost in loss record 2 of 2 ==236601== at 0x484880F: malloc (vg_replace_malloc.c:446) ==236601== by 0x491158E: strdup (strdup.c:42) ==236601== by 0x10922A: Person_create (ex16.c:18) ==236601== by 0x109381: main (ex16.c:48) ==236601== ==236601== LEAK SUMMARY: ==236601== definitely lost: 21 bytes in 2 blocks ==236601== indirectly lost: 0 bytes in 0 blocks ==236601== possibly lost: 0 bytes in 0 blocks ==236601== still reachable: 0 bytes in 0 blocks ==236601== suppressed: 0 bytes in 0 blocks ==236601== ==236601== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

这一次,向Person_print传递NULL,并且观察Valgrind会输出什么。

⮞ ./ex16 Joe is at memory location 0x55e046cba2a0: Name: Joe Alex Age: 32 Height: 64 Weight: 140 Frank is at memory location 0x55e046cba2e0: Name: Frank Blank Age: 20 Height: 72 Weight: 180 Name: Joe Alex Age: 52 Height: 62 Weight: 180 Name: Frank Blank Age: 40 Height: 72 Weight: 200 Segmentation fault
==237517== Invalid read of size 8 ==237517== at 0x1092CA: Person_print (ex16.c:36) ==237517== by 0x109459: main (ex16.c:68) ==237517== Address 0x0 is not stack'd, malloc'd or (recently) free'd ==237517== ==237517== ==237517== Process terminating with default action of signal 11 (SIGSEGV) ==237517== Access not within mapped region at address 0x0 ==237517== at 0x1092CA: Person_print (ex16.c:36) ==237517== by 0x109459: main (ex16.c:68) ==237517== If you believe this happened as a result of a stack ==237517== overflow in your program's main thread (unlikely but ==237517== possible), you can try to increase the size of the ==237517== main thread stack using the --main-stacksize= flag. ==237517== The main thread stack size used in this run was 8388608. ==237517== ==237517== HEAP SUMMARY: ==237517== in use at exit: 1,093 bytes in 5 blocks ==237517== total heap usage: 5 allocs, 0 frees, 1,093 bytes allocated ==237517== ==237517== LEAK SUMMARY: ==237517== definitely lost: 0 bytes in 0 blocks ==237517== indirectly lost: 0 bytes in 0 blocks ==237517== possibly lost: 0 bytes in 0 blocks ==237517== still reachable: 1,093 bytes in 5 blocks ==237517== suppressed: 0 bytes in 0 blocks ==237517== Rerun with --leak-check=full to see details of leaked memory ==237517== ==237517== For lists of detected and suppressed errors, rerun with: -s ==237517== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) Segmentation fault

附加题

将这个程序改为不用指针和malloc的版本

在这个练习的附加题中我想让你尝试一些有难度的东西:将这个程序改为不用指针和malloc的版本。这可能很困难,所以你需要研究下面这些东西:

  • 如何在栈上创建结构体,就像你创建任何其它变量那样。

  • 如何使用x.y而不是x->y来初始化结构体。

  • 如何不使用指针来将结构体传给其它函数。

#include <stdio.h> #include <string.h> struct Person { char name[50]; int age; int height; int weight; }; void Person_print(struct Person who) { printf("Name: %s\n", who.name); printf("\tAge: %d\n", who.age); printf("\tHeight: %d\n", who.height); printf("\tWeight: %d\n", who.weight); } int main(int argc, char *argv[]) { // create two people structures directly on the stack struct Person joe; strcpy(joe.name, "Joe Alex"); joe.age = 32; joe.height = 64; joe.weight = 140; struct Person frank; strcpy(frank.name, "Frank Blank"); frank.age = 20; frank.height = 72; frank.weight = 180; // print them out and where they are in memory printf("Joe is at memory location %p:\n", (void*)&joe); Person_print(joe); printf("Frank is at memory location %p:\n", (void*)&frank); Person_print(frank); // make everyone age 20 years and print them again joe.age += 20; joe.height -= 2; joe.weight += 40; Person_print(joe); frank.age += 20; frank.weight += 20; Person_print(frank); return 0; }

在这个版本中:

- 直接在栈上创建 struct Person 实例 joefrank

- 使用 strcpy 来复制字符串到 name 字段,因为不能再使用 strdup

- Person_createPerson_destroy 函数被移除,因为不再需要动态分配和释放内存。