源代码:
#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;
}
部分理解:
当我们注释掉如下两行代码时:
Person_destroy(joe);
Person_destroy(frank);
编译后再次运行:
valgrind ./ex16 --leak-check=full
会有如下输出(关注标红的两行):
==3556==
==3556== HEAP SUMMARY:
==3556== in use at exit: 69 bytes in 4 blocks
==3556== total heap usage: 5 allocs, 1 frees, 1,093 bytes allocated
==3556==
==3556== LEAK SUMMARY:
==3556== definitely lost: 48 bytes in 2 blocks
==3556== indirectly lost: 21 bytes in 2 blocks
==3556== possibly lost: 0 bytes in 0 blocks
==3556== still reachable: 0 bytes in 0 blocks
==3556== suppressed: 0 bytes in 0 blocks
==3556== Rerun with --leak-check=full to see details of leaked memory
==3556==
==3556== For lists of detected and suppressed errors, rerun with: -s
==3556== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
48 bytes指的是结构体 joe 与 frank 所占用的24个字节(age、height、weight);
21 bytes指的是结构体 joe 与 frank 的name所占用的另外的21个字节,因为使用了strdup(),其中"Joe Alex"占用9个,"Frank Blank"占用12个。
因为malloc实在堆上分配内存。所以需要释放内存。
另外:
"Definitely lost":这表示某些内存块被分配后完全没有被释放,而且程序也没有保留任何指向这些内存的指针。这意味着你完全失去了对这些内存块的控制,无法再访问它们。
这些内存泄漏是明确的。
"Indirectly lost":这是指某些内存块本身是通过指针间接引用的,但由于指向它们的主指针(即直接引用的内存块)已经被泄漏,这些间接引用的内存块也无法被释放。换句话说,如果主内存块泄漏了,所有通过该块间接引用的内存也会泄漏。
这些泄漏通常是因为你没有释放负责指向它们的主内存块,导致这些内存块的间接引用也丢失。也就是说malloc();中name位置存的是strdup();返回的指针。
Valgrind等工具报告内存泄漏时,主要关注的是通过指针指向的动态内存,因此指针不会被报告。
接着我们做如下修改,将NULL传到Person_print();中
Person_print(joe); --> Person_print(NULL);
会有如下报错:
Joe is at memory location 0x4a98040:
==3702== Invalid read of size 8
==3702== at 0x1092CA: Person_print (ex16.c:36)
==3702== by 0x1093B9: main (ex16.c:53)
==3702== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==3702==
==3702==
==3702== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==3702== Access not within mapped region at address 0x0
==3702== at 0x1092CA: Person_print (ex16.c:36)
==3702== by 0x1093B9: main (ex16.c:53)
告诉我们main函数调用的Person_print函数的第36行发生错误——访问了地址0x0,也就是一个空指针,产生了段错误(Segmentation fault)。
- 将这个程序改为不用指针和malloc的版本
- 如何在栈上创建结构体,就像你创建任何其它变量那样
- 如何使用x.y而不是x->y来初始化结构体
- 如何不使用指针来将结构体传给其它函数
思考:
栈上创建结构体:直接声明结构体变量,不使用 malloc 或 free。当直接声明一个结构体变量同时不使用 malloc(); ,它就是在栈上创建的。
. 和 -> 的区别
. 运算符:用于访问栈上结构体或通过直接变量名访问结构体成员。
-> 运算符:用于访问堆上结构体或指针类型结构体的成员。例如,joe.age 表示访问栈上结构体 joe 的 age 成员。
例如,joe->age 表示访问一个指针 joe 所指向的结构体的 age 成员。
在 C 语言中,使用 . 来访问结构体成员的情况是当你有一个结构体的实例而不是指向结构体的指针。-> 用于通过指针访问结构体成员,而 . 用于通过结构体变量直接访问成员。
因此,要使用 x.y 代替 x->y 初始化结构体,关键是要直接在栈上声明和使用结构体实例,而不是在堆上使用 malloc 动态分配结构体。
可以直接将结构体传递给函数,函数接收到的是结构体的副本,而不是引用(即指针)。
函数 Person_create 创建了一个新的 struct Person 结构体,并返回一个指向该结构体的指针。malloc(sizeof(struct Person));语句是用于动态分配内存,在堆上分配内存空间来存储结构体的实际数据,并创建一个指向 struct Person 结构体的指针。
修改后的代码,在调用函数Person_print时会将结构体的副本传递给函数,而不是直接传递结构体本身,过程在栈中操作。
代码如下:
#include <stdio.h>
struct Person {
char *name;
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[])
{
// make two people structures
struct Person joe = {
.name = "Joe Alex",
.age = 32,
.height = 64,
.weight = 140
};
struct Person frank = {
.name = "Frank Blank",
.age = 20,
.weight = 72,
.height = 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;
}
总之:
在 Person_create 函数中,我们动态分配了内存,因为我们需要在堆上创建一个新的结构体,并返回指向该结构体的指针。
在 Person_print 函数中,我们使用了结构体的副本,因此在栈上创建的结构体副本会被传递给函数,并在函数结束后自动被销毁,无需手动释放内存。这种方式可以避免手动分配和释放内存的麻烦,但需要注意副本的创建和传递可能会占用更多的内存空间。