C 总线错误 (bus error) - 段错误 (segmentation fault)

C 总线错误 (bus error) - 段错误 (segmentation fault)

两个常见的运行时错误

  • bus error (core dumped) - 总线错误 (信息已转储)
  • segmentation fault (core dumped) - 段错误 (信息已转储)

错误信息对引起这两种错误的源代码错误并没有作简单的解释,上面的信息并未提供如何从代码中寻找错误的线索,而且两者之间的区别也并不是十分清楚,时至今日依然如此。

错误就是操作系统所检测到的异常,而这个异常是尽可能地以操作系统方便的原则来报告的。总线错误和段错误的准确原因在不同的操作系统版本上各不相同。这里所描述是运行于 SPARC 架构的 SunOS 出现的这两类错误以及产生错误的原因。

当硬件告诉操作系统一个有问题的内存引用时,就会出现这两种错误。操作系统通过向出错的进程发送一个信号与之交流。信号就是一种事件通知或一个软件中断,在 UNIX 系统编程中使用很广,但在应用程序编程中几乎不使用。在缺省情况下,进程在收到总线错误段错误信号后将进行信息转储并终止。不过可以为这些信号设置一个信号处理程序 (signal handler),用于修改进程的缺省反应。

信号是由于硬件中断而产生的。对中断的编程是非常困难的,因为它们是异步发生的 (其发生时间是不可预测的)。阅读信号的主文档和头文件 usr/include/sys/signal.h

1. 在 PC 上捕捉信号

信号处理函数是 ANSI C 的一部分,与 UNIX 一样,它也同样适用于 PC。例如 PC 程序员可以使用 signal() 函数来捕捉 Ctrl-Break 信号,防止用户用这种方法中断程序。

在任何使用信号的源文件中,都必须在文件前面增加一行 #include <singal.h>

这条信息的 core dumped 部分来源于很早的过去,那时所有的内存都是由铁氧化物圆环 (也就是 core,指磁心) 制造的。半导体成为内存的主要制造材料的时间已经超过十五年,但 core 这个词仍然被用作内存的同义词。

core [kɔː(r)]:n. 核心,要点,果心,磁心 vt. 挖...的核

2. 总线错误 (bus error)

事实上,总线错误几乎都是由于未对齐的读或写引起的。它之所以称为总线错误,是因为出现未对齐的内存访问请求时,被堵塞的组件就是地址总线。对齐 (alignment) 的意思就是数据项只能存储在地址是数据项大小的整数倍的内存位置上。在现代的计算机架构中,尤其是 RISC 架构,都需要数据对齐,因为与任意的对齐有关的额外逻辑会使整个内存系统吏大且更慢。通过迫使每个内存访问局限在一个 Cache 行或一个单独的页面内,可以极大地简化 (并加速) 如 Cache 控制器和内存管理单元这样的硬件。

我们表达“数据项不能跨越页面或 Cache 边界”规则的方法多少有些问接,因为我们用地址对齐这个术语来陈述这个问题,而不是直截了当说是禁止内存跨页访问,但它们说的是同一回事。例如,访问一个 8 字节的 double 数据时,地址只允许是 8 的整数倍。所以一个 double 数据可以存储于地址 24、8008 或 32768,但不能存储于地址 1006 (因为它无法被 8 整除)。页和 Cache 的大小是经过精心设计的,这样只要遵守对齐规则就可以保证一个原子数据项不会跨越一个页或 Cache 块的边界。

2.1 引起总线错误的程序

//============================================================================
// Name        : main
// Author      : Yongqiang Cheng
// Version     : Version 1.0.0
// Copyright   : Copyright (c) 2019 Yongqiang Cheng
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <stdio.h>

int main(int argc, char *argv[])
{
	union union_name
	{
		char a[10];
		int i;
	} union_object;

	printf("argc = %d\n", argc);

	for (int idx = 0; idx < argc; ++idx)
	{
		printf("argv[%d] --> %s\n", idx, argv[idx]);
	}

	printf("argv[argc] = %p\n\n", (void*)argv[argc]);

	int *pt = (int *)&(union_object.a[1]);
	int *pi = (int *)&(union_object.i);

	*pt = 17;

	printf("*pt = %d\n", *pt);
	printf("pt = %p\n", pt);
	printf("pi = %p\n", pi);

	return 0;
}
argc = 1
argv[0] --> D:\visual_studio_workspace\yongqiang\Debug\yongqiang.exe
argv[argc] = 00000000

*pt = 17
pt = 008FFA39
pi = 008FFA38
请按任意键继续. . .

在这里插入图片描述

扫描二维码关注公众号,回复: 10302696 查看本文章

pt 中未对齐的地址会引起一个总线错误!
在实际的运行中并没有出现错误,运行环境如下:
在这里插入图片描述

这可能导致一个总线错误,因为数组和 int 的联合确保数组 a 是按照 int 的 4 字节对齐的,a+l 的地址肯定未按 int 对齐。我们试图往这个地址存储 4 个字节的数据,但这个访问只是按照单字节的 char 对齐,这就违反了规则。一个好的编译器发现不对齐的情况时会发出警告,但它并不能检测到所有不对齐的情况。

编译器通过自动分配和填充数据 (在内存中) 来进行对齐。当然,在磁盘或磁带上并没有这样的对齐要求,所以程序员对它们可以很偷快地不必关心数据对齐。但是,当他们把一个 char 指针转换为 int 指针时,就会出现神秘的总线错误。几年前,当检测到一个内存奇偶检验错误时也会产生总线错误。现在,内存芯片已经非常可靠,而且很好地得到了错误检测和修正电路的保护,所以在应用程序编程这一级,奇偶检验错误几乎不再听闻。总线错误也可能由于引用一块物理上不存在的内存引起。如果不遭遇一个淘气的驱动程序,你恐怕不大可能遭遇这种不幸。

发布了525 篇原创文章 · 获赞 1873 · 访问量 116万+

猜你喜欢

转载自blog.csdn.net/chengyq116/article/details/105186781