无malloc的内存读取

无malloc的内存读取,c,memory-management,compiler-optimization,undefined-behavior,C,Memory Management,Compiler Optimization,Undefined Behavior,我编写了一个C程序,如下所示: void foo(int *a) { if (a[1000] == a[1000]) { printf("Hello"); } } int main() { int *a; foo(a); return 0; } 我原以为这个程序会崩溃,因为我没有在&a[1000]分配内存,但程序实际上没有崩溃并打印Hello。我用命令编译了这个程序 gcc -O0 foo.c 原因可能是什么?未定义行为的副作用之一是预期输出 但这并不能证明U

我编写了一个C程序,如下所示:

void foo(int *a) {
  if (a[1000] == a[1000]) {
    printf("Hello");
  } 
}

int main() {
  int *a;
  foo(a);
  return 0;
}
我原以为这个程序会崩溃,因为我没有在&a[1000]分配内存,但程序实际上没有崩溃并打印Hello。我用命令编译了这个程序

gcc -O0 foo.c

原因可能是什么?

未定义行为的副作用之一是预期输出


但这并不能证明UB是定义的

访问未分配的内存位置是未定义的行为

现在,如果程序访问的内存受到限制,则这可能会导致seg故障

或者,就像你的情况一样,它不会有任何明确的效果。它可能正在读取以前程序留下的垃圾值。这种行为称为未定义


它可能会在您的情况下工作一段特定的时间,但肯定不会一直工作。

您的程序也可能会崩溃或不崩溃

它不崩溃的事实并不意味着它可以工作。实际上,这是一种未定义的行为,意味着任何事情都可能发生。它可以读取一些随机值,也可以由于分段错误而崩溃。因此,当您测试它时,它现在工作的事实并不意味着它将始终工作

例如,您可以尝试运行您的程序几次,您可能会遇到segfault


这是因为语言标准没有规定很多内容。

之所以没有崩溃,一个解释是编译器可能优化掉了[1000]==a[1000],因为这个表达式总是正确的

尝试使用[1000]!=a[1001]也许你每次都会撞车


但无论如何,这是未定义的行为。

这里的行为未定义,这里您试图访问内存,您不知道。这个随机内存位置可能保存着一些关键数据,也可能只是一个可以使用的好位置。 案例1:您的程序将因分段错误而崩溃。 案例2:你的程序可以很好地打印Hello World。 由于模棱两可的方案不是一个很好的方案,我们不采取这种做法。 现在我们有了更好的操作系统,您只会遇到分段错误,否则在该程序可能导致系统崩溃之前的几天内

这里int*a是一个堆栈变量,因为堆栈变量没有预先初始化,所以它包含一些垃圾值

幸运的是,这个垃圾值在允许的地址或程序内,因此程序没有恐慌。

TL;博士 正如大家已经注意到的,访问越界内存是非常困难的。然而,在这种特殊情况下发生了一些非常有趣的事情,使您的程序根本无法访问内存。死代码被删除了

虽然不能保证,但大多数高质量的编译器都会优化if1{…}或if0{…},这与gcc的情况完全相同,即使在-O0中也是如此。检查并确认

逻辑推理 您的编译器正在基于简单逻辑优化if条件,这就是为什么它总是使用-O0标志工作的原因。这种内存访问永远不会发生。当编译器找到一个[1000]==a[1000],或者实际上是一个[n]==a[n]时,它知道它本质上与表示VAR==VAR是相同的,这对于任何变量都是相同的,对于任何变量都是正确的。它来自并被称为,它表示任何元素A都等于它自己。我不知道是否有一个特定的优化标志,但我不认为有特别的,因为它发生在-O0中。如果有人知道,请在评论中告诉我

换句话说,编译器将ifa[1000]==a[1000]替换为if1,这始终是真的,因此它将完全删除if

需要注意的是,访问越界内存始终是未定义的行为,但是,在这种情况下,翻译后的代码永远不会访问任何内存。为了证明这一点,一些反汇编代码:

使用gcc-O0-ofoo.c编译提供的代码输出以下foo函数:

(gdb) disass foo
Dump of assembler code for function foo:
   0x000000000040052d <+0>: push   %rbp
   0x000000000040052e <+1>: mov    %rsp,%rbp
   0x0000000000400531 <+4>: sub    $0x10,%rsp
   0x0000000000400535 <+8>: mov    %rdi,-0x8(%rbp)
   0x0000000000400539 <+12>:mov    $0x4005f4,%edi
   0x000000000040053e <+17>:mov    $0x0,%eax
   0x0000000000400543 <+22>:callq  0x400410 <printf@plt>
   0x0000000000400548 <+27>:leaveq 
   0x0000000000400549 <+28>:retq   
End of assembler dump.
布尔西!等等!如果在哪里?我在这里没有看到任何cmp说明,也没有看到任何其他类型的分支。。。。如果你离开了。它实际上不是GCC的优化选项,而是逻辑优化。1总是等于1。编译器在输出机器代码之前就知道了这一点,因此您的if从未访问过二进制文件,也没有访问过内存

但是,如果要执行ifa[1000]==a[1001]并使用相同的gcc-O0-o foo foo.c进行编译,您将得到以下foo:


请注意,在尝试执行400543处的指令后,它会崩溃。400543是什么?0x0000000000400543:mov%rax,%edx。这正是它试图访问超出范围内存的地方。繁荣这是您未定义的行为。

未定义的行为是未定义的。仅仅因为它似乎有效并不意味着它实际上可以按预期工作。它甚至可以访问内存吗?”x==x'对所有x都是真的,那为什么要麻烦呢?我没看就穿过了街道
两条路都有,而且没有被公共汽车撞倒。为什么不呢?“过马路总是安全的吗?”马丁·詹姆斯说得没错。内存访问在这段代码中从未发生过,GCC甚至在-O0中也优化了这个条件。我发布了一个答案,显示了这一点。在这个程序可能导致系统崩溃之前的几天。真正地只是想知道这个小小的错误是否真的会使系统崩溃。真棒的答案:。但是有一个注意事项-a[1000]==a[1000]可能实际上不是真的,因为负载不是原子的。其他线程可能会改变它。然而,编译器认为,每次我运行时都会出现一个等式,这是一个可能且有效的结果,因此我被允许让它总是发生,并继续删除代码。感谢您的精彩回答!显然,对于这个问题,存在的不仅仅是未定义的行为,这是唯一一个确定并详细解释它的答案。@Lee或编译器认为它不是易失性的,因此允许缓存它。
(gdb) print (char*)0x4005f4
$3 = 0x400614 "Hello"
(gdb) disass foo
Dump of assembler code for function foo:
   0x000000000040052d <+0>: push   %rbp
   0x000000000040052e <+1>: mov    %rsp,%rbp
   0x0000000000400531 <+4>: sub    $0x10,%rsp
   0x0000000000400535 <+8>: mov    %rdi,-0x8(%rbp)
   0x0000000000400539 <+12>:mov    -0x8(%rbp),%rax
   0x000000000040053d <+16>:add    $0xfa0,%rax
   0x0000000000400543 <+22>:mov    (%rax),%edx
   0x0000000000400545 <+24>:mov    -0x8(%rbp),%rax
   0x0000000000400549 <+28>:add    $0xfa4,%rax
   0x000000000040054f <+34>:mov    (%rax),%eax
   0x0000000000400551 <+36>:cmp    %eax,%edx
   0x0000000000400553 <+38>:jne    0x400564 <foo+55>
   0x0000000000400555 <+40>:mov    $0x400614,%edi
   0x000000000040055a <+45>:mov    $0x0,%eax
   0x000000000040055f <+50>:callq  0x400410 <printf@plt>
   0x0000000000400564 <+55>:leaveq 
   0x0000000000400565 <+56>:retq   
End of assembler dump.
Breakpoint 1, 0x0000000000400531 in foo ()
(gdb) stepi
0x0000000000400535 in foo ()
(gdb) stepi
0x0000000000400539 in foo ()
(gdb) stepi
0x000000000040053d in foo ()
(gdb) stepi
0x0000000000400543 in foo ()
(gdb) stepi

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400543 in foo ()