提问人:Nathaniel Woodbury 提问时间:7/29/2023 最后编辑:Nathaniel Woodbury 更新时间:7/30/2023 访问量:114
C 初学者 - 结构中的字符串不需要 Malloc
Beginner to C - Malloc Not Required For String In Struct
问:
为什么不需要在结构中的字符串上调用 Malloc?
嗨,我这周刚开始学习 C,我发现了一个我无法解释的奇怪不一致。
据我所知,当函数结束时,“堆栈”中分配的项目将从内存中删除。如果我们希望一个变量持续更长时间(当作用域结束时),我们使用 malloc。
这对结构来说是完全有意义的,并且是一致的。如果您创建一个结构,则尝试打印其创建函数范围之外的值 - 除非您恶意定位该结构,否则将导致错误。
但是,我发现如果我尝试创建一个结构并使用默认值(结构的大小)对其进行 malloc,然后添加一个 char 指针(字符串)作为结构成员,它会接收该值并且在尝试打印它时不会出现任何错误 - 我认为我需要在结构成员本身上调用 malloc, 然后将字符串复制到其中?
知道这里发生了什么吗??
这将打印结构成员,而不会出现任何问题。
typedef struct person {
char *name;
} person;
// print struct fields
void print_person(person *input) { printf("Name: %s", input->name); }
person *person_generator(char *name) {
my_struct *person = malloc(sizeof(my_struct));
// assign given char pointer to struct field
person->name = name;
return person;
}
person *create_default_person() {
person *new_person = person_generator("Alfred Hitchcock");
return new_person;
}
int main() {
person *default_person = create_default_person();
print_person(default_person);
return 0;
}
我很困惑为什么下面的代码不是强制性的?
typedef struct person {
char *name;
} person;
// print struct fields
void print_person(person *input) { printf("Name: %s", input->name); }
person *person_generator(char *name) {
my_struct *person = malloc(sizeof(my_struct));
// didnt need to do this?
person->name = malloc(strlen(name) + 1);
strcpy(person->name, name);
return person;
}
char *get_string() {
char *item = "Alfred Hitchcock";
return item;
}
person *create_default_person() {
char *value = get_string();
person *new_person = person_generator(value);
return new_person;
}
int main() {
person *default_person = create_default_person();
print_person(default_person);
return 0;
}
我期望当“create_default_person()”函数结束时,它创建的指针会从其内部的堆栈中被销毁,那么结构成员应该指向垃圾数据,对吗?
编辑
附加问题:
我被告知答案是字符串文字在应用程序的生命周期内存储。让我们再举一个例子。从文件中读取字符串,然后将其分配到 struct 字段中,而不调用 malloc。
为什么这仍然正确打印
typedef struct person {
char *name;
} person;
void print_person(person *input) { printf("Name: %s", input->name); }
person *person_generator(char *name) {
my_struct *person = malloc(sizeof(my_struct));
person->name = name;
return person;
}
person *create_default_person() {
FILE *fp;
fp = fopen("./data.txt", "r");
char buffer[255];
if (fp == NULL) {
printf("Couldnt open\n");
return NULL;
}
fgets(buffer, 255, fp);
fclose(fp);
person *new_person = person_generator(buffer);
return new_person;
}
int main() {
person *human = create_default_person();
if (human == NULL) {
printf("Failed to find human");
return 0;
}
print_person(human);
return 0;
}
答:
在 C 语言中,字符串文字 (like ) 作为进程静态数据的一部分静态存储。"Alfred Hitchcock"
因此,它们可以保证在进程的整个生命周期内保持有效,并且不需要手动分配或释放其存储空间。正如你所发现的,一个简单的指向它们的指针就足以使用它们。
另一方面,如果你在运行时随机编造新名称,则需要为这些运行时生成的字符串显式分配空间,并使用 /etc 管理它们的生存期。malloc()/free()
我期望当“create_default_person()”函数结束时,它创建的指针会从其内部的堆栈中被销毁,那么结构成员应该指向垃圾数据,对吗?
不完全是 -- 当函数结束时,指针会被销毁,但指针指向的数据不会。在这种情况下,字符串文本本身无限期地保持有效,无论指向或不指向它。
评论
create_default_person
"Alfred Hitchcock"
person_generator
person_generator
person->name
person
create_default_person
new_person
据我所知,当函数结束时,“堆栈”中分配的项目将从内存中删除。如果我们希望一个变量持续更长时间(当作用域结束时),我们使用 malloc。
只要将代码加载到内存中,函数中声明的任何值都将存在。也许你不能再接受它了,但价值会一直存在。在对同一函数的下一次调用中,它们将具有相同的值和地址。这对于字符串文字是一样的,如 。static
item
char *item = "Alfred Hitchcock"
通过调用在函数内部分配的区域不会被神奇地删除:这是 的任务,你的代码负责:必须由你的代码分配的区域,通过在从函数返回之前调用,或者使用程序逻辑和返回的指针。否则,该地区将永远消失。这称为内存泄漏malloc()
free
malloc
free
但是,我发现如果我尝试创建一个结构并使用默认值(结构的大小)对其进行 malloc,然后添加一个 char 指针(字符串)作为结构成员,它会接收该值并且在尝试打印它时不会出现任何错误 - 我认为我需要在结构成员本身上调用 malloc, 然后将字符串复制到其中?
关于这个
这是struct
typedef struct person
{
char* name;
} person;
person
只是指向 char 的指针的容器。并且具有固定的尺寸。你不需要对它做任何事情。它可以指向一个 1 GB 的区域,它可以指向一个 .它可以指向 。如果你需要一些东西,你需要把它放在那里。double
NULL
有点跑题了,但是,正如您正在学习的那样,您会看到 .并且有一个无声的约定,保留第一个大写字母来命名你定义的名字,所以你可以更好地作为struct
C
person
typedef struct
{
char* name;
} Person;
另请注意,编译器并不关心代码的对齐方式。缩进仅用于阅读。符号是一种宗教信仰,我们很少可以选择如何在源代码中声明或对齐事物:这是由您的学校、公司或客户决定的。通常,我们甚至无法决定使用哪种语言。
但现实情况是,当你在 in 中声明时,你实际上是在声明你的 IDE 或编译器可以告诉你的 a,所以你这样写可能更容易name
char* name
name
char*
typedef struct
{
char* name;
} Person;
而且从不
typedef struct
{
char *name;
} Person;
这里是 .它被宣布为这样。如果 是 那么显然是 : 这就是 这里 的意思。这是声明的结果,而不是声明本身的结果。name
char*
name
char*
*name
char
*
C
以这种方式编写代码可以使以后更容易阅读和理解代码。
关于第一个代码
void print_person(Person* input)
{
printf("Name: %s", input->name);
}
没关系,但检查一个是明智的NULL
input
person *person_generator(char *name) {
my_struct *person = malloc(sizeof(my_struct));
// assign given char pointer to struct field
person->name = name;
return person;
}
这在某种程度上也没关系,但正如我之前告诉过你的那样,最好按原样写
Person* person_generator(char* name)
{
Person* one = malloc(sizeof(Person));
one->name = name; // can be NULL
return one;
}
这里的问题是,以这种方式写作,你无法控制你在里面放什么。请参阅下面的代码,我稍后会谈到这一点struct
这里
person *create_default_person() {
person *new_person = person_generator("Alfred Hitchcock");
return new_person;
}
没关系,只是一个默认值,但请考虑
Person* create_default_person()
{
return person_generator("Alfred Hitchcock");
}
它更容易阅读。
返回person_generator
如果不将字符串内容复制到指向的新分配区域,则会遇到麻烦。如果不这样做,则无法安全地释放此区域。如果被召唤,你最终可能会打电话,而且它是无害的。但是,如果被调用,就像你在 中所做的那样,这个字符串是静态的,一个文字,不能是自由的,所以如果你释放 .更好的是始终拥有您的数据,就像在name
person_generator (NULL)
free(NULL)
person_generator("Alfred Hitchcock")
create_default_person()
Person
struct
Person
Person* create_person(char* name)
{
Person* one = (Person*)malloc(sizeof(Person));
if (name == NULL) return one;
one->name = (char*)malloc(sizeof(strlen(name) + 1));
if (name == NULL) return one; // if malloc failed
strcpy(one->name, name); // copy all data
printf("\tAllocating \"%s\"\n", one->name);
return one;
}
[名称改为create_person()
person_generator
]
很多人会告诉你不要结果。这是 90 年代的东西,基于 95 年的一本书和几年后的 C 常见问题解答文档。
这在古代的东西中:不要相信它。正如你在这里看到的,即使在上面这个适度的 10 行代码中,有时你也需要在一页代码中做很多事情,而转换返回的指针会作为对你和你的员工的文档或警告。 只是返回,并且通过不让转换隐含,您可以为自己省去很多麻烦。今天我们知道,隐性的东西不是这么好的......cast
malloc
malloc
malloc
void*
另外,当你编写代码来创建一个动态的东西时,请习惯于立即编写代码来删除它。如
Person* erase_person(Person* one)
{
if (one == NULL) return NULL;
if (one->name != NULL)
printf("\tDeleting \"%s\"\n", one->name);
free(one->name);
free(one);
return NULL;
}
并测试它。
一个完整的例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
char* name;
} Person;
Person* create_default_person();
Person* create_person(char*);
Person* erase_person(Person*);
void print_person(Person*);
int main(void)
{
Person* five[5] = {0};
for (int i = 0; i < 5; i += 1)
five[i] = create_default_person();
printf(" Persons:\n");
for (int i = 0; i < 5; i += 1) print_person(five[i]);
printf(" Deleting all Persons\n");
five[0] = erase_person(five[0]);
for (int i = 0; i < 5; i += 1)
five[i] = erase_person(five[i]);
five[0] = create_person("Isac Asimov");
print_person(five[0]);
five[0] = erase_person(five[0]);
return 0;
}
Person* create_default_person()
{
return create_person("Alfred Hitchcock");
}
Person* erase_person(Person* one)
{
if (one == NULL) return NULL;
if (one->name != NULL)
printf("\tDeleting \"%s\"\n", one->name);
free(one->name);
free(one);
return NULL;
}
Person* create_person(char* name)
{
Person* one = (Person*)malloc(sizeof(Person));
if (name == NULL) return one;
one->name = (char*)malloc(sizeof(strlen(name) + 1));
if (name == NULL) return one; // if malloc failed
strcpy(one->name, name); // copy all data
printf("\tAllocating \"%s\"\n", one->name);
return one;
}
void print_person(Person* input)
{
if (input == NULL) return;
printf("\tName: %s\n", input->name);
}
这是输出
Allocating "Alfred Hitchcock"
Allocating "Alfred Hitchcock"
Allocating "Alfred Hitchcock"
Allocating "Alfred Hitchcock"
Allocating "Alfred Hitchcock"
Persons:
Name: Alfred Hitchcock
Name: Alfred Hitchcock
Name: Alfred Hitchcock
Name: Alfred Hitchcock
Name: Alfred Hitchcock
Deleting all Persons
Deleting "Alfred Hitchcock"
Deleting "Alfred Hitchcock"
Deleting "Alfred Hitchcock"
Deleting "Alfred Hitchcock"
Deleting "Alfred Hitchcock"
Allocating "Isac Asimov"
Name: Isac Asimov
Deleting "Isac Asimov"
该代码只是创建一个指针数组,用它做一些事情并删除每一个。看看如何使用这些东西可能会有所帮助。Person
从第二个代码
char *get_string() {
char *item = "Alfred Hitchcock";
return item;
}
person *create_default_person() {
char *value = get_string();
person *new_person = person_generator(value);
return new_person;
}
我期望当“create_default_person()”函数结束时,它创建的指针会从其内部的堆栈中被销毁,那么结构成员应该指向垃圾数据,对吗?
退货何时结束其生命周期。但是 a 会返回给调用方。该指针指向一个静态字符串文字“Alfred Hitchcock”,它将永远存在。get_string()
item
char*
当结束并到达他们的一生时。但
指针将返回给调用方。该指针指向一个有效的指针,其中有一个指向内部声明的静态数据的指针,该字符串literal用于初始化。create_default_person()
value
new_person
Person
struct
get_string()
item
关于附加问题
这个程序x.c
char* alfred = "alfred";
int main(void){ return 0; }
生成此程序集代码
.LC0:
.string "alfred"
alfred:
.quad .LC0
main:
push rbp
mov rbp, rsp
mov eax, 0
pop rbp
ret
当从此代码生成可执行文件时,指令和字符串的数据肯定会记录到 .当您运行程序时,会加载到内存中,并包含一个区域,一个带有字符串的区域,如您在可执行文件中看到的:x.exe
x.exe
x.exe
static
dump
0010760 0000 0000 0000 0000 0000 0000 0000 0000
\0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0011020 6c61 7266 6465 0000 0000 0000 0000 0000
a l f r e d \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0011040 ffff ffff ffff ffff ffff ffff ffff ffff
377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377
0011060 0140 0000 0000 0000 0000 0000 0000 0000
@ 001 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
您会在 address 上看到一个预期的字符串。当加载到内存中时,字符串将具有一个固定的静态地址,该地址相对于代码加载到由操作系统运行的地址的开头,这将是您可以使用 访问的地址。11020
alfred
&alfred[0]
更改示例
#include <stdio.h>
#include <string.h>
const char* alfred = "alfred";
int main(void)
{
// alfred[0] = 'A'; running this crashes the program
// as the area can not be changed.
// Declaring it 'const' makes the
// compiler work for you and do not
// allow the program to run and crash
printf(
" \"alfred\" is at 0x%X. Last byte is %d\n",
&alfred[0], alfred[strlen(alfred)]);
const char* p = alfred;
printf(
" pointer points to 0x%X, value there is "
"\"%s\" Last byte is %d\n",
p, p, *(p + strlen(p)));
return 0;
}
代码如下:
- 打印加载字符串的地址,并显示最后一个字节为 a。
null
- 声明指向此地址的指针,并使用它来打印相同的内容。在第二个 printf 上,您会看到指针算术作为获取字符串最后一个字节的替代方法
C
- 请注意,该区域被声明为 and undestest,这对您有好处,因此编译器不会让您出错并尝试更改静态文本,而是如果以后只是使您的代码崩溃
const char*
评论
malloc
person->name = strdup(name);
delete_person(default_person)
free(person->name);
person->name
malloc()
person->name = name;
person->name = malloc(strlen(name) + 1); strcpy(person->name, name);
print_person()
char buffer[255]
)