有人可以帮助我了解这个C程序有什么问题吗?
#include <stdio.h>
#include <stdlib.h>
struct Box {
int **value;
};
void nop(void) {
/* Why does this function have side effects? */
void *a = malloc(sizeof *a);
free(a);
}
struct Box *makeBox(void) {
int *value = NULL;
struct Box *box = malloc(sizeof *box);
box->value = &value;
return box;
}
int main(void) {
struct Box *box = makeBox();
printf("Dereferenced: %pn", *box->value);
nop();
printf("Dereferenced: %pn", *box->value);
}
如果我运行它,它会打印:
Dereferenced: (nil)
Dereferenced: 0x562831863727
但是,如果我注释掉nop
函数,我会得到:
Dereferenced: (nil)
Dereferenced: (nil)
有人可以帮助我理解为什么打电话nop
会改变*box->value
吗?
基本上,nop()
函数具有"副作用",只是由于使用堆栈。问题出在makeBox()
函数中,该函数正在设置指向堆栈变量的指针。我在片段中添加了一些评论:
struct Box *makeBox(void) {
// value is an integer pointer on the stack
int *value = NULL;
struct Box *box = malloc(sizeof *box);
// box->value is set to the address of the stack location of value
box->value = &value;
return box;
}
当nop()
在堆栈上分配a
时,它实际上是在踩踏另一个指针所在的堆栈。这是一个示例,说明为什么不能返回指向堆栈变量的指针,因为该指针不会保留在分配它的函数的范围之外。
程序具有未定义的行为。main
box->value
指针包含不确定的值。在makeBox
里面,它曾经指向一个局部变量value
,但现在makeBox
已经完成,这个局部变量永远消失了。尝试取消引用它作为*box->value
会导致未定义的行为。这就是你观察到的。
使用您发布的代码(为了可读性而略有修改:
#include <stdio.h>
#include <stdlib.h>
struct Box
{
int **value;
};
void nop(void)
{
/* Why does this function have side effects? */
void *a = malloc(sizeof *a);
free(a);
}
struct Box *makeBox(void)
{
int *value = NULL;
struct Box *box = malloc(sizeof *box);
box->value = &value;
return box;
}
int main(void)
{
struct Box *box = makeBox();
printf("Dereferenced: %pn", *box->value);
nop();
printf("Dereferenced: %pn", *box->value);
}
编译时始终启用警告。 然后修复这些警告。 (对于gcc
,至少使用:
-Wall -Wextra -Wconversion -pedantic -std=gnu11
这将导致编译器输出几条警告消息。
(仅仅因为消息说"警告"而不是"错误"并不意味着可以忽略警告。
警告:
:13:29: 警告:对空类型"sizeof"的应用无效 [-Wpointer-arith]
:30:12: 警告:格式"%p"需要类型参数 'void *',但参数 2 的类型为 'int *' [-Wformat=]
:32:12: 警告:格式"%p"需要类型为"void *"的参数,但参数 2 的类型为"int *" [-Wformat=]
编辑:makebox()
函数返回局部变量的地址。 当函数退出时,局部变量超出范围。 所以这是未定义的行为。
调用函数时,上述未定义的行为是一个问题:nop()
,因为它使用的堆栈区域与"makebox(("函数中使用的堆栈区域相同。
局部变量:void *a
使用与makebox()
函数变量value
相同的堆栈部分。
因此,当main()
函数取消引用struct Box, which points to the same area on the stack, and that area has been modified by the later call to
nop((' 中的字段时,堆栈上的值(该区域(已更改。
因此,对printf()
的调用将在第一次调用时拾取原始值,在第二次调用时将拾取从呼叫返回的地址malloc()
即 修改代码,使其没有任何未定义的行为。 这可以通过更改struct box
以使其包含实际数据或移动局部变量来完成:value
到某个"安全区域",例如为其提供文件范围,也许通过在变量声明之前插入修饰符static