我一直在涉及一些c代码,并初始化了像这样的猫结构
typedef struct
{
int age;
char *name;
char *favoriteQuote;
} Cat;
我创建了两个函数,一个用于初始化cat对象,另一个用于清零看起来像这样的内存
Cat initialize_cat_object(void)
{
Cat my_cat;
my_cat.age = 3;
my_cat.favorite_quote = "A day without laughter is a day wasted";
my_cat.name = "Chester";
return my_cat;
}
Cat destroy_cat_object(void)
{
Cat my_cat;
memset(&my_cat, 0, sizeof(my_cat));
//--forgot to return 'my_cat' here--
}
我的主要功能如此
void main(void)
{
Cat my_cat;
my_cat = initialize_cat_object();
printf("Creating cat\n")
printf("Name: %s\nFavoriteQuote: %s\nAge: %d\n", my_cat.name,
my_cat.favorite_quote, my_cat.age);
my_cat = destroy_cat_obect();
printf("CAT DESTRUCTION\n");
printf("Name: %s\nFavoriteQuote: %s\nAge: %d\n", my_cat.name,
my_cat.favorite_quote, my_cat.age);
}
直到我回到源代码,我注意到我忘记返回内存被清零的Cat对象,但程序仍显示预期的输出,但是如果我尝试省略return语句'initialize_cat_object'函数,数据输出已损坏
我唯一能想到的是'destroy_cat_object'返回归零的内存,但这怎么可能呢?
答案 0 :(得分:3)
destroy_cat_object
没有return
声明。 C11 6.9.1p12说:
然而,对于具有返回类型的函数,但是在结束括号之前没有<{em} 具有如果到达了终止函数的
}
,并且调用者使用了函数调用的值,则行为是未定义的。
return
语句,这是完全正常的C标准。调用这样的函数也完全可以。
但是,如果函数没有以显式返回值的return
语句终止,那么使用函数调用的返回值是不正确的。
如果没有收到相关消息,您可能希望在编译器设置中启用一些额外的诊断。
答案 1 :(得分:2)
将注册放在一边。您的数据可以存储在C中的三个位置:
在你的情况下,我们谈论的是堆栈。 Stack是一个LIFO队列元素,只要它们没有弹出就是有效且可访问的。所以如果你有这样的功能:
typedef struct {
int a_val;
float b_val;
char c_val;
} a_t;
a_t* func(void) {
a_t a = {1, 1., 'a'};
return &a;
}
“a”将驻留在堆栈中,直到func返回,因此在func返回后,它的指针变为无效并指向堆栈空间中的某个位置。在大多数系统上,堆栈不会归零,因此在其他一些数据覆盖它之前,可能会通过该指针获取一些数据,这可能会产生误导。
那你该怎么办?像这样:
void initialize_cat(Cat*);
void clear_cat(Cat*);
int main() {
Cat my_cat;
initialize_cat(&my_cat);
// do kitty stuff
clear_cat(&my_cat); // cat's private data must not be compromised
}
当函数返回一个结构时,这实际上是通过调用者的合作实现的(我在这里说的是SysV x64 ABI,对于其他情况可能是错误的)。基本上,调用者在堆栈上分配足够的空间来存储返回的结构,并将指针作为隐式的第一个参数传递给它。 callee正在使用此指针稍后写入数据。
所以这两个案例:
Cat callee(void) {
Cat my_cat = { .age = 5 };
return cat;
}
void caller(void) {
Cat my_cat = callee();
}
和
void callee(Cat *my_cat) {
my_cat->age = 5;
return cat;
}
void caller(void) {
Cat my_cat;
callee(&my_cat);
}
几乎一样。