Malloc 改变单独函数中未初始化变量的行为?
Malloc altering behavior of uninitialized variable in separate function?
这是我的编程语言 Concepts/Implementation class 的问题。给定以下 C 代码片段:
void foo()
{
int i;
printf("%d ", i++);
}
void main()
{
int j;
for (j = 1; j <= 10; j++)
foo();
}
foo 中的局部变量 i 从未初始化,但在大多数系统上的行为类似于静态变量。这意味着程序将打印 0 1 2 3 4 5 6 7 8 9。我明白为什么这样做(i的内存位置永远不会改变)但是作业中的问题要求修改代码(不改变foo ) 来改变这种行为。我想出了一个有效的解决方案,使程序打印十个 0,但我不知道它是否是 "right" 解决方案,老实说我不知道确切知道它为什么有效。
这是我的解决方案:
void main()
{
int j;
void* some_ptr = NULL;
for (j = 1; j <= 10; j++)
{
some_ptr = malloc(sizeof(void*));
foo();
free(some_ptr);
}
}
我最初的想法是 i 没有改变位置,因为在 foo 的调用周围没有发生其他内存操作,所以分配一个变量应该会破坏它,但是因为 some_ptr 分配在堆中并且 i 在堆栈上,应该' some_ptr 的分配对 i 没有影响吗?我的想法是编译器正在玩一些优化子程序调用的游戏,有人可以澄清一下吗?
foo中的变量i只是未初始化,未初始化的值在进入块时具有不确定的值。您看到它打印特定值的方式完全是巧合,要编写符合标准的 C,您永远不应该依赖这种行为。您应该始终在使用自动变量之前对其进行初始化。
来自 c11std 6.2.4p6:
对于这样一个没有可变长度数组类型的对象,它的生命周期从进入与其关联的块开始,直到该块的执行以任何方式结束为止。 (进入封闭块或调用函数会暂停但不会结束当前块的执行。)如果以递归方式进入块,则每次都会创建对象的新实例。对象的初始值是不确定的。如果为对象指定了初始化,则在块的执行中每次到达声明或复合文字时都会执行初始化;否则,每次到达声明时该值都变得不确定。
不可能有 "right" 解决方案。但是可以有 class 解决方案适用于特定 CPU 架构、ABI、编译器和编译器选项。
将代码更改为类似这样的代码会以某种方式改变堆栈上方的内存,从而以有针对性的方式影响许多(如果不是大多数的话)环境。
void foo()
{
int i;
printf("%d ", i++);
}
void main()
{
int j;
int a [2];
for (j = 1; j <= 10; j++)
{
foo();
a [-5] = j * 100;
}
}
输出(Linux 上的 gcc x64):
0 100 200 300 400 500 600 700 800 900
a[-5]
是用于跨越两个函数的开销和变量的堆栈字数。有 return 地址,保存的堆栈 link 值等。当 foo() 写入 a[-5]:
时,堆栈可能看起来像这样
i
saved stack link
return address
main's j
(must be something else)
main's a[]
我第二次猜到了-5。 -4 是我的第一个猜测。
当您从 main()
调用 foo()
时,(未初始化的)变量 i
被分配到一个内存地址。在原始代码中,碰巧它是零(在你的机器上,你的编译器,你选择的编译选项,你的环境设置,并给定当前的月相——它可能会改变,当任何这些,或无数其他因素,变化)。
通过在调用 foo()
之前调用另一个函数,您允许另一个函数用不同的值覆盖 foo()
将用于 i
的内存位置。不保证会改变;运气不好,你可以用另一个零替换零。
您也许可以使用另一个函数:
static void bar(void)
{
int j;
for (j = 10; j < 20; j++)
printf("%d\n", j);
}
并在调用 foo()
之前调用它会更改 i
中的值。调用 malloc()
也会改变一些事情。调用几乎任何函数都可能会改变它。
但是,必须(再次)强调原始代码充满了未定义的行为,调用其他函数并不会减少未定义的程度。任何事情都可能发生,而且是有效的。
未初始化的值似乎保留了过去调用的值的原因是它在堆栈上,并且每次调用函数时堆栈指针恰好具有相同的值。
您的代码可能更改该值的原因是您开始调用其他函数:malloc
和 free
。它们的内部堆栈变量使用与 foo()
.
中的 i
相同的位置
至于优化,像这样的小程序有完全消失的危险。 GCC 或 Clang 可能 决定由于使用未初始化的变量是未定义的行为,编译器有权完全删除代码。或者它可能会将 i
放入寄存器中并设置为零。然后决定所有 printf 调用输出零。然后确定您的整个程序只是一个 puts("0000000000")
调用。
这是我的编程语言 Concepts/Implementation class 的问题。给定以下 C 代码片段:
void foo()
{
int i;
printf("%d ", i++);
}
void main()
{
int j;
for (j = 1; j <= 10; j++)
foo();
}
foo 中的局部变量 i 从未初始化,但在大多数系统上的行为类似于静态变量。这意味着程序将打印 0 1 2 3 4 5 6 7 8 9。我明白为什么这样做(i的内存位置永远不会改变)但是作业中的问题要求修改代码(不改变foo ) 来改变这种行为。我想出了一个有效的解决方案,使程序打印十个 0,但我不知道它是否是 "right" 解决方案,老实说我不知道确切知道它为什么有效。
这是我的解决方案:
void main()
{
int j;
void* some_ptr = NULL;
for (j = 1; j <= 10; j++)
{
some_ptr = malloc(sizeof(void*));
foo();
free(some_ptr);
}
}
我最初的想法是 i 没有改变位置,因为在 foo 的调用周围没有发生其他内存操作,所以分配一个变量应该会破坏它,但是因为 some_ptr 分配在堆中并且 i 在堆栈上,应该' some_ptr 的分配对 i 没有影响吗?我的想法是编译器正在玩一些优化子程序调用的游戏,有人可以澄清一下吗?
foo中的变量i只是未初始化,未初始化的值在进入块时具有不确定的值。您看到它打印特定值的方式完全是巧合,要编写符合标准的 C,您永远不应该依赖这种行为。您应该始终在使用自动变量之前对其进行初始化。
来自 c11std 6.2.4p6:
对于这样一个没有可变长度数组类型的对象,它的生命周期从进入与其关联的块开始,直到该块的执行以任何方式结束为止。 (进入封闭块或调用函数会暂停但不会结束当前块的执行。)如果以递归方式进入块,则每次都会创建对象的新实例。对象的初始值是不确定的。如果为对象指定了初始化,则在块的执行中每次到达声明或复合文字时都会执行初始化;否则,每次到达声明时该值都变得不确定。
不可能有 "right" 解决方案。但是可以有 class 解决方案适用于特定 CPU 架构、ABI、编译器和编译器选项。
将代码更改为类似这样的代码会以某种方式改变堆栈上方的内存,从而以有针对性的方式影响许多(如果不是大多数的话)环境。
void foo()
{
int i;
printf("%d ", i++);
}
void main()
{
int j;
int a [2];
for (j = 1; j <= 10; j++)
{
foo();
a [-5] = j * 100;
}
}
输出(Linux 上的 gcc x64):
0 100 200 300 400 500 600 700 800 900
a[-5]
是用于跨越两个函数的开销和变量的堆栈字数。有 return 地址,保存的堆栈 link 值等。当 foo() 写入 a[-5]:
i
saved stack link
return address
main's j
(must be something else)
main's a[]
我第二次猜到了-5。 -4 是我的第一个猜测。
当您从 main()
调用 foo()
时,(未初始化的)变量 i
被分配到一个内存地址。在原始代码中,碰巧它是零(在你的机器上,你的编译器,你选择的编译选项,你的环境设置,并给定当前的月相——它可能会改变,当任何这些,或无数其他因素,变化)。
通过在调用 foo()
之前调用另一个函数,您允许另一个函数用不同的值覆盖 foo()
将用于 i
的内存位置。不保证会改变;运气不好,你可以用另一个零替换零。
您也许可以使用另一个函数:
static void bar(void)
{
int j;
for (j = 10; j < 20; j++)
printf("%d\n", j);
}
并在调用 foo()
之前调用它会更改 i
中的值。调用 malloc()
也会改变一些事情。调用几乎任何函数都可能会改变它。
但是,必须(再次)强调原始代码充满了未定义的行为,调用其他函数并不会减少未定义的程度。任何事情都可能发生,而且是有效的。
未初始化的值似乎保留了过去调用的值的原因是它在堆栈上,并且每次调用函数时堆栈指针恰好具有相同的值。
您的代码可能更改该值的原因是您开始调用其他函数:malloc
和 free
。它们的内部堆栈变量使用与 foo()
.
i
相同的位置
至于优化,像这样的小程序有完全消失的危险。 GCC 或 Clang 可能 决定由于使用未初始化的变量是未定义的行为,编译器有权完全删除代码。或者它可能会将 i
放入寄存器中并设置为零。然后决定所有 printf 调用输出零。然后确定您的整个程序只是一个 puts("0000000000")
调用。