是什么导致这个错误是不确定的

What causes this bug to be non-deterministic

最近写了如下,bug,c代码:

#include <stdio.h>

struct IpAddr {
  unsigned char a, b, c, d;
};

struct IpAddr ipv4_from_str (const char * str) {
  struct IpAddr res = {0};
  sscanf(str, "%d.%d.%d.%d", &res.a, &res.b, &res.c, &res.d);
  return res;
}

int main (int argc, char ** argv) {
  struct IpAddr ip = ipv4_from_str("192.168.1.1");
  printf("Read in ip: %d.%d.%d.%d\n", ip.a, ip.b, ip.c, ip.d);
  return 0;
}

错误是我在 sscanf 中使用 %d,同时提供指向 1 字节宽的无符号字符的指针。 %d接受一个4字节宽的int指针,不同导致越界写入。越界写肯定是ub,程序会崩溃

我的困惑在于错误的非恒定性。超过 1,000 次运行,程序 50% 的时间在打印语句 之前 出现段错误,另外 50% 的时间在 之后 语句出现段错误。我不明白为什么这会改变。程序的两次调用有什么区别?我的印象是堆栈的内存布局是一致的,我编写的小测试程序似乎证实了这一点。不是这样吗?

我在 Debian 书虫、内核 5.14.16-1 上使用 gcc v11.3.0,并且我编译时没有设置任何标志。

Here是我编译器的汇编输出,供参考。

未定义的行为意味着任何事情都可能发生,甚至是不一致的结果。

实际上,这种不一致很可能是由于 Address Space Layout Randomization。根据数据在内存中的位置,out-of-bounds 访问可能会或可能不会访问未分配的内存或覆盖关键指针。

另见 Why don't I get a segmentation fault when I write beyond the end of an array?