【问题标题】:Reverting to previous values in a memory zone in C恢复到 C 中存储区中的先前值
【发布时间】:2015-01-03 23:55:03
【问题描述】:

我玩了一点C,写了如下代码:

#include<stdio.h>
#include<stdlib.h>
int main() {
    char* value = malloc(5 * sizeof(char));
    int vect[3];
    printf("%d\n", value[135151]);
    int i, count = 0;
    for(i = 0; i < 135152; i++) {
        if(value[i]) {
            count++;
            printf("position is %d, value is %d and i change it with 42\n", i, value[i]);
            value[i] = 42;
            vect[count - 1] = i;
        }
    }
    printf("count is %d\n", count);

    printf("pointer is at location %p\n", value);
    printf("changed values are %d %d %d\n", value[vect[0]], value[vect[1]],
                                                        value[vect[2]]);

    return 0;
}

经过几次尝试,在我的笔记本电脑上,我发现如果我打印 value[135152] 我得到 segfault,如果我打印 value[135151] 我在标准输出得到 0。

在那之后,我很好奇 查找此间隔中是否存在非零值,并显示 3 个非零值。

之后,我尝试将它们全部修改为 42(我忘了提到在许多程序执行时,20+,即使向量值显示在不同的位置,例如 0xbe7010 或 0x828010,相同的非零相同位置的值仍然存在,这让我明白指针地址是虚拟的(但位置相同))。

之后,我修改了这些值,为了确定,我最后打印了它们,它们显示了 42 个,全部 3 个。但是,在另一个程序执行时,显示了以前的值,就像我没有修改那个内存区域一样。

我会给你我的 3 个连续输出:

0
position is 24, value is -31 and i change it with 42
position is 25, value is 15 and i change it with 42
position is 26, value is 2 and i change it with 42
count is 3
pointer is at location 0x21bb010
changed values are 42 42 42


0
position is 24, value is -31 and i change it with 42
position is 25, value is 15 and i change it with 42
position is 26, value is 2 and i change it with 42
count is 3
pointer is at location 0x20d1010
changed values are 42 42 42


0
position is 24, value is -31 and i change it with 42
position is 25, value is 15 and i change it with 42
position is 26, value is 2 and i change it with 42
count is 3
pointer is at location 0x19d0010
changed values are 42 42 42

你能告诉我为什么这些价值观在改变之后仍然存在吗?

还有,为什么指针地址变了,但内存区是一样的? (我怀疑 C 中的物理内存和虚拟内存之间存在双射函数,每次执行程序时都会发生变化。

感谢您的帮助,并为这堵文字墙感到抱歉!

【问题讨论】:

  • 代码应始终检查 malloc(和系列)的返回值,以确保操作成功
  • 这一行:'printf("%d\n", value[135151]);'正在访问分配区域之外的内存。这会导致未定义的行为,从而导致段错误事件。
  • 这一行:'if(value[i]) {' 当 'i' 大于 4 时,这会导致未定义的行为,这可能会导致 seg 故障事件。执行未定义的行为时会发生什么是“未定义的”。它可以是任何东西,包括你看到的行为。注意:在同一个循环中还有两个未定义行为的实例。与其担心某些随机事件是可重复的,不如修复程序。
  • 您是在标准 C 的上下文中询问(在这种情况下,答案是“未定义的行为意味着可能发生奇怪的事情”)还是在整个系统的上下文中(“在这个特定的操作系统上版本,堆的位置是随机的,但之后的一切都是确定性的,并且总是在相对于堆的同一个地方分配东西,所以其他代码相对于你的分配在同一个地方写东西......“)跨度>

标签: c memory allocation


【解决方案1】:

如果我正确理解了您的问题,那么答案比您想象的要简单得多。在每次程序执行时,malloc 通过向操作系统请求内存来分配内存,但malloc 和操作系统都没有任何特别的理由在每次程序执行时为您提供相同的内存空间。据我所知,如果您没有将程序作为内核级程序运行,那么确实没有任何方法可以确保每次都获得相同的内存地址,即使在这种情况下,您也可能不会拥有相同的内存地址值,因为其他程序可能在此期间已写入内存(或计算机已关闭,因为malloc 从易失性内存分配内存)。您正在寻找的可能是将数组保存到文件并在程序执行时从该文件中读取。

您所做的几乎所有事情都是未定义的,因此对于为什么会发生的问题,我无法给您一个充分的答案。以下是您正在调用的一些不同类型的未定义行为:

printf("%d\n", value[135151]);

读取数组末尾,或更正式地取消引用任何偏移量大于数组大小(value[5] 或更高)的指针是未定义的行为。我已经让许多程序甚至取消引用导致段错误,但在这种情况下,您的操作系统或初始化库(程序在main 之前运行的东西)似乎正在为您的程序分配一堆内存而无需您询问.

if(value[i])

变量或内存空间中没有被你的程序赋值的值是未定义的。如果所有内存都为零,或者它具有以前碰巧存在的任何值,那将是完全合法的。关于读取此类内存的问题,您的操作系统显然在它被释放之后或在它被分配给您的程序之前为其分配了特定的值。操作系统可能这样做的一个重要原因是安全性 - 如果一个程序从它之前的程序获取输入到内存中的值,它可以读取其他程序的数据,如果之前的程序将明文密码转换为哈希值,这将非常糟糕,例如。

value[i] = 42;

这可能不言而喻,但是将值分配给未分配给您的内存位置也是未定义的行为。

编辑:回应评论:未定义的行为意味着标准没有定义当你这样做时会发生什么。显然,如果一个程序编译并运行,它必须表现出一些行为,但未定义的行为可能会因编译器、标准库版本、操作系统和各种其他因素而完全不同。在您的情况下,所有变量都聚集在一起导致您看到的行为,但是如果没有分解有关您的编译器、环境、硬件等的每个细节,我们无法告诉您原因,更重要的是,该行为可能(并且可能会)完全不同,例如,如果我使用不同的操作系统和编译器编译和运行这段代码。

附带说明一下,我在几个编译器上进行了尝试,在 Linux Red Hat 上的 clang 3.5.0 上得到了完全相同的结果,并且与 gcc 类似,所以我最好的猜测是该信息与 @987654330 有关@ 的实现,可能是 free 在释放内存时使用的元数据。

【讨论】:

  • 为什么每次打印的值都是-31 15 2
  • 在每个执行程序中,我确实喜欢 20,我会以相同的值(我的意思是非零值)获得相同位置的值,这让我思考,我仍然认为,在这种情况下,C 在每次执行时都给了我相同的内存区域。因此,为了利用这一点,我想将非零值覆盖为其他值(认为它可能会为我分配另一个内存区域)。但是,惊喜!它为我分配了相同的内存区域,甚至更多,我覆盖的值保持在以前的值。
  • 几乎你所做的一切都是未定义的,所以我不能给你一个足够的答案来回答这个问题。我将尝试在我的回答中为您详细介绍未定义行为的每个实例。
  • 以前,我使用 malloc 分配了 2 个字符数组,一个大小为 3,一个大小为 4。我发现 vect1[32] 是 vect2[0],因为我分配了 vect2[0]一个非零值,从 char 到 char 开始 vect1[0] 直到我弄清楚了。在我将 vect1[32] 更改为另一个值后, vect2[0] 也发生了更改。那也是未定义的行为吗?我仍然无法理解未定义的行为(回到我在此处发布的代码的问题)如何导致相同位置的相同值保持相同的值(在我看来太巧合了)。
  • 我认为你应该让“你所做的几乎所有事情都是未定义的行为”评论你答案的开头段落。任何关于会发生和不会发生的猜测纯属巧合;行为 is 未定义,一切皆有可能(包括似乎有效)。 (您可以查看我的answer 到另一个问题,以了解我所说的关于调用未定义行为的不那么令人震惊的情况(但行为要么是未定义的,要么是已定义的——或者偶尔是未指定的——而且程度并不重要)。
【解决方案2】:

我假设您对“未定义的行为意味着任何事情都可能发生”不满意,并且想了解 C 本身未定义的事情。因为不是C定义的,以下是部分推测:

地址空间布局随机化(ASLR)是大多数现代操作系统的一项功能。它随机化程序中每个主要内存区域的起始地址。其目的是使某些类型的安全漏洞更难被利用。这不在这个问题的范围内。

另外,还有在main 之前运行的代码。它初始化标准库中的各种东西。当我说“你的程序”时,我包括了这段代码。

由于 ASLR,堆将在每个进程中从不同的地址开始。但是,由于您的程序不使用任何其他随机性(除了 ASLR,它是确定性),它总是以相同的大小和顺序分配内存块。由于malloc 在您的操作系统上不是随机的——除了堆的起始地址——它以相同的“模式”分配内存。也许你的内存块总是在(start_of_heap + 123400),而其他东西总是在(start_of_heap + 123424) 获得一个内存块——在这种情况下,另一个内存块总是(your_memory_block + 24),即使确切的地址不同。

另一个内存块是什么?我不可能准确地猜到这一点——但鉴于你的程序没有崩溃,你的程序很可能再也不会使用了。对于您未使用的某些功能,它可能很重要,或者它可能是内存分配器的簿记信息(它永远不会看到您覆盖的值,因为在那之后您永远不会调用 mallocfree)。

附:覆盖不属于您的内存是导致无法预料的崩溃的好方法。您应该认真避免在任何真正的程序中这样做。这也是编写不可移植程序的好方法——也许 OSX 在那个位置存储了更重要的东西,或者它甚至没有分配那个位置(所以你在访问它时会出现段错误)。

【讨论】:

  • 我做这个程序是为了学习一些关于 C 的东西,所以这些“错误”是为了好奇之后发生的事情而故意做的。所以,基本上,你的意思是那些给我的值,我试图修改但没有工作,我的意思是位置 24、25 和 26 上的值可能会以某种方式被内存分配器使用。如果是这样,为什么 C 没有一些规则规定所有未分配的东西在访问时都应该给出段错误?我之前说过,我可以使用 char* vect1 修改 char* vect2,方法是找出错误的 vect1[32]==vect2[0]。
  • @Emanuel C 的设计理念的一部分是不限制程序员做“坏”的事情,在他们已经完成作业并且知道自己在做什么的极端情况下。例如,在 Java 中,如果您分配一个数组,然后尝试读取其最后一个索引,它总是会抛出异常。在这种情况下,编译器也不可能确切知道您为数组分配了多少空间,因为该信息存储在运行时由malloc 确定的位置,并且可能在运行时确定。
  • 所以在 Java 中分配是在编译时完成的(这有助于确定我是否读到了它的索引,但结果会使程序变慢),而在 C 中它是在运行时完成的,主要是为了速度考虑?另外,由于有一些元数据可以帮助“释放”函数知道要释放多少空间,难道这些信息不能用来确定我是在未分配的索引上读还是写吗?
  • @Emanuel 您不会因访问不属于您的内存而出现段错误,因为 CPU 怎么知道您是否应该访问该内存? C 设计理念的另一部分是轻量级——当你编写value[135131] 时,它会被编译成address = (variable "value") + 135131; read memory at "address" 之类的东西。这两个都是完全有效的操作——第一个只是加法,第二个是内存访问。
  • @Emanuel 从理论上讲,只要您访问对象边界之外,编译器就有可能崩溃,但我不知道有任何编译器会崩溃。主要是因为它会使所有程序慢几倍,而且还会浪费内存。
猜你喜欢
  • 1970-01-01
  • 2019-04-25
  • 1970-01-01
  • 1970-01-01
  • 2019-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多