最近,我写了以下,buggy,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指针,这个差异会导致越界写入.越界写入肯定是错误的,程序会崩溃.

我的困惑在于这个错误的非恒定性.在超过before0次运行时,程序SEGbefore在50%的时间内对打印语句进行故障诊断,SEGafter在另外50%的时间内对语句进行故障诊断.我不明白为什么这会改变.程序的两次调用之间有什么区别?我的印象是堆栈的内存布局是一致的,我编写的小测试程序似乎证实了这一点.不是吗?

我使用的是gcc v11.Debian bookworm上的3.0,内核5.14.16-1,我编译时没有设置任何标志.

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

推荐答案

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

在实践中,这种不一致很可能是由于Address Space Layout Randomization.根据数据在内存中的位置,越界访问可能会也可能不会访问未分配的内存或覆盖关键指针.

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

C++相关问答推荐

当包含头文件时,gcc会发出隐式函数声明警告

理解没有返回语句的递归C函数的行为

什么C代码将确定打开的套接字正在使用的网络适配器?

从内联程序集调用Rust函数和调用约定

为什么我得到更多的256假阳性在PKZIP解密密钥验证?

如何在C宏中确定Windows主目录?

如何在IF语句中正确使用0.0

Can函数指针指向C++中具有不同参数连续性的函数

为什么该函数不将参数值保存到数据 struct 中?

如何在ASM中访问C struct 成员

在创建动态泛型数组时,通过realloc对故障进行分段

是否可以通过调用两个函数来初始化2D数组?示例:ARRAY[STARTING_ROWS()][STARTING_COLUMNS()]

Square不与Raylib一起移动

如何用c语言修改shadow文件hash部分(编程)?

S在本文中的价值观到底出了什么问题?

如何在不使用字符串的情况下在c中编写函数atof().h>;

发送和接收的消息中的Unix域套接字不匹配

将char*数组深度复制到 struct 中?

我可以使用Windows SDK';s IN6_IS_ADDR_LOOPBACK等,尽管没有文档?

strlen 可以是[[未排序]]吗?