我的操作系统如何知道定义的变量是否已初始化?

How does my OS know if a defined variable is initialized or not?

提问人:LeChummpy 提问时间:11/13/2023 最后编辑:LeChummpy 更新时间:11/13/2023 访问量:88

问:

目前,我正在用 C 语言中的排序算法来搞砸一些。在此过程中,我遇到了以下问题: 我定义了一个 int 数组

int array[LENGTH];

其中 LENGTH 是全局定义的变量。 然后我尝试更改数组的第一个元素。

array[0] = 1;

使用 gcc 编译并执行时,我遇到了分段错误。使用 valgrind 执行二进制文件后,我出现以下错误:

Conditional jump or move depends on uninitialised value(s)

当我再次运行 valgrind 时,现在带有标志 ,valgrind 将我引导到定义初始数组的确切行。--track-origins=yes

在堆上做同样的事情不会改变任何事情:

int* array = (int*) malloc(LENGTH*sizeof(int));
*(array+0) = 1;
...
free(array)

但是,我总是被告知,我的操作系统不区分已初始化和未初始化的变量,而只是读取存储在分配的存储段上的任何垃圾,无论数组地址(在堆栈或堆上)的值是否被设置。

现在我很困惑。我的系统如何识别某个位置的变量是否已初始化?还是我走在完全错误的路径上,堆栈和堆上的存储仅在初始化变量时分配(这将解释很多)?

感谢您的帮助!

数组 C 堆栈 valgrind

评论

1赞 Scott Hunter 11/13/2023
int array[0] = 1;在我看来不像是合法的C。
1赞 Vlad from Moscow 11/13/2023
@LeChummpy 这一行 int array[0] = 1;不是赋值语句。这是对具有 0 个元素的数组的错误声明。
0赞 LeChummpy 11/13/2023
对不起,这只是一个错别字,在我的实际代码中它不是那样的。
1赞 Vlad from Moscow 11/13/2023
@LeChummpy 提供重现问题的最小完整程序。

答:

2赞 Marco Bonelli 11/13/2023 #1

我的系统如何识别某个位置的变量是否已初始化?

操作系统不知道。

然而,Valgrind 模拟您的程序执行的机器代码(指令),并意识到您正在从您以前从未写过任何内容的内存地址中读取某些内容(并且该地址不属于在程序启动时自动初始化的程序段),因此它将其视为读取未初始化的数据。

如果在检测到读取未初始化数据后,Valgrind 遇到依赖于此类数据的指令,它将发出警告,例如您看到的警告。

评论

0赞 LeChummpy 11/13/2023
好的,谢谢。因此,这意味着不是使用未初始化的值,而是其他原因导致了 seg 错误,对吗?
0赞 Marco Bonelli 11/13/2023
@LeChummpy不,可能是使用值或依赖于它的其他东西。很难说,这取决于手头的具体情况。读取未初始化的数据是未定义的行为,因此在运行时可能会发生任何事情。
0赞 OrangeDog 11/13/2023
@LeChummpy未初始化的值通常用作内存地址,如果它是尚未分配的位置,则会导致段错误
0赞 John Bollinger 11/13/2023 #2

我的系统如何识别某个位置的变量是否已初始化?

您的系统不知道。Valgrind 知道您何时在其监督下运行程序,因为它会监视。这是昂贵的,有时甚至是极其昂贵的。

还是我走在完全错误的路径上,堆栈和堆上的存储仅在初始化变量时分配(这将解释很多)?

我不确定我是否明白你的意思。从语义上讲,当控制到达自动变量的声明时,会为自动变量分配空间。在程序执行开始之前,为静态持续时间变量分配空间。当且仅当声明包含初始值设定项时,才会在分配自动变量时初始化自动变量的空间。

评论

0赞 LeChummpy 11/13/2023
好的,这回答了我的最后一个问题,谢谢。
1赞 Paul Floyd 11/13/2023 #3

Valgrind,在本例中,是一个相当完整的虚拟化环境。它取代了对 malloc 的调用,因此它可以跟踪堆内存分配和解除分配。它跟随堆栈指针,因此它知道堆栈上何时有可用空间。它知道所有寄存器的状态。memcheck

除了堆内存、堆栈内存和寄存器之外,它还为所有这些保持影子状态。在最简单的级别上,这将是一个位图,该位图对存储器/寄存器的每个字节都有一个位(已初始化或未初始化)。每次读取或写入时,该影子状态都会更新。这意味着它始终知道初始化了哪些内存/寄存器。

当您的测试可执行文件具有调试信息(使用或类似信息编译)时,Valgrind 可以读取该信息,以允许它计算出与导致问题的变量声明相关的源文件的名称和行号。-g

严格来说,Linux 上的堆内存在写入之前不会分配。这由内核处理,通常是透明的(除非你用完内存并且 OOM 杀手启动)。