讨论一下 总线错误(bus error)

maenxiang 2003-11-10 07:13:46
我们经常会发现有两种内存转储(core dump)
一种是段错误(segment error)通常是在一个非法的地址上进行取值赋值操作造成。
一种是总线错误(bus error)通常时指针强制转换,导致CPU读取数据违反了一定的总线规则。

下面请大家讨论一下总线错误

有例子如下sizeof(int)==4:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i[5];
int j;
i[0]=65536+2;
i[1]=65536*3+4;
j=*((int *)((char *)i+2)); /* 1 bus error */
printf("size of int is %d\nj=%d",sizeof(int),j);
return 0;
}

我们姑且不考字节序问题,不管j结果是几.

在一般RISC的CPU上,一般的unix机器上都会出现bus error。
而在windows机器上,我用了vc的cl borland的bcc32和gnu的gcc编译执行都没问题。

大家有兴趣可以讨论一下

...全文
7339 7 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
ChinaPlayer 2004-01-27
  • 打赏
  • 举报
回复
mark
gridcomputing 2003-11-17
  • 打赏
  • 举报
回复
login :)
liao2001 2003-11-13
  • 打赏
  • 举报
回复
不知道你用的是哪种unix,我记得在Solaris是会存在类似的bus error的。
我在linux和sco下进行一些测试,即使你采用编译器默认的对齐方式,对于奇地址强制转化为int地址的情况,程序运行正常,在linux和sco下编译器从栈中为非对齐的结构体非配空间的方式有点小差异,如上所述的结构体,linux是从偶地址开始,sco是从奇地址开始。。。

撇开上面这些先不谈。
1。每个操作系统都会有自己的进程载入和调度程序,也就是说,虚内存到实内存的映射,每种os都会有自己的特点,他们可以根据自己的情况避开某些小概率事件,“边界”也应该可以称之为小概率事件,在分配空间时做点手脚,对于分配者来说应该不会困难。
2。对齐问题,很久以前就听别人说,是为了提高访问效率,想想也是,多一种情况就不得不“if”,而且对齐也利于硬件“批量”访问数据。
3。采用非对齐方式会降低程序的效率,有些书上可能会提到这些,建议让编译器来决定采用哪种方式,毕竟每种编译器都是根据系统而来的。
4。在进行地址转换取值的时候,尽量用位拷贝,这样不同的os也不用当心。
5。不管os怎样对待bus error问题,不管编译器如何优化,硬件访问数据时的边界问题始终存在,开发可移植系统时,我认为不应该漏掉这个问题。

你的疑问:
1。pi->a对于开发者来说是int,对于c编译起来说是int,对于汇编器来说是一段空间,对于硬件来说是一段空间,在目标代码一级,没什么所谓的类型。
2。cpu访问数据是通过发指令来控制硬件访问数据的,通常是先读到cache、寄存器。形象一点的话,和十字路口的红绿灯的控制下的车流差不多。看《微机原理》或许会有所帮助。
3。我认为,硬件在遇到边界问题时,应该会有警告,各各os处理不同罢了。
4。Solaris的特点

建议:暂时不用钻得太深,除非你有足够的资料。知道这个问题和大致的可能原因就差不多了,不是说不去掌握它,而是计算机这个东西内容太多,很多硬件问题采用避开而不是花很大代价去解决。等有了足够的知识积累,相信这个问题不会是什么难事。
maenxiang 2003-11-12
  • 打赏
  • 举报
回复
继续

讨论一下结构体内各域不对齐的情况,如下代码:
#ifdef WIN32
#pragma pack (1)
#else
#pragma pack 1
#endif
typedef struct
{
char c;
int a;
}TS;

int main()
{
int i[5];
int j;
TS *pi;
pi=(TS *)(i);
printf("size of TS is %d\n",sizeof(TS));
j=pi->a; /* 1 */
printf("p of i is %p\n",i);
printf("p of pi->a is %p\n",&(pi->a));
return 0;
}
在windows上好使,结果如下:
size of TS is 5
p of i is 0012FED0
p of pi->a is 0012FED1

在UNIX上也好使,结果如下:(使用较高版本的编译器,否则不认识#pragma pack)
size of TS is 5
p of i is 7f7f0690
p of pi->a is 7f7f0691

一下疑问是基于UNIX的:
这时候的pi->a到底是什么类型?
CPU是怎么样访问该数据的?
为什么就没有总线错误了?
再将j=pi->a; 改为j=*((int *)(&(pi->a))); 立马出现总线错误


liao2001 2003-11-12
  • 打赏
  • 举报
回复
呵呵,这就很奇怪了,这或许就和我的猜测一样,windows的虚内存和实内存在映射上和unix不同;也有可能是windows根本就不捕获这个信号,在windows下有很多信号是没有的,如果他根本就不捕获这个信号的话,哪也就不会bus error了,不过这样岂不是有隐含的问题?

后一种可能性比较大,因为硬件对此类地址应该会产生信号的,而比尔老头采用的处理方法可能是“do nothing”,据说windows很多东东是根据unix而来的。
maenxiang 2003-11-12
  • 打赏
  • 举报
回复
一下代码
int main()
{
int a[4]={0,0,0,0};
int *pi;
pi=(int *)(((char *)a)+1);
*pi=3;
printf("a0 is %d\n" "a1 is %d\n" "pi is %d\n",a[0],a[1],*pi);
printf("sizeof int is %d\n",sizeof(int));
return 0;
}
同样用GCC编译
HP_UX上是出总线错误,(其他UNIX机器上也出,以前做过)
但拿到WINDOWS 2000机器上就好使。
a0 is 768
a1 is 0
pi is 3
sizeof int is 4
现不考虑字节序问题,为什么在windows上就好使呢,难道编译出来的不是32位的程序。难道还作了什么特殊的处理。理论上已改出错才对。怪异
liao2001 2003-11-11
  • 打赏
  • 举报
回复
我想,说来说去bus error都是一个“边界”问题。以下是我的看法:
从硬件的角度来看,大家应该发现内存是一小块一小块的吧,每一块都有固定大小,现在应该都是4的整数倍,又或者说int大小的整数倍,又或者说地址线总数的整数倍。为什么要这样?硬件好处理呀。举个例子来说吧,如果int型为四字节大小,且首字节地址不受限制的话,该int就可能跨在2个内存块之间(又或者别人说的“跨在2个页内存之间”),那么硬件如何来取数据呢?熟悉硬件的人都应该知道,内存访问是用“十字交叉”来决定地址的,也就是说,每个内存块是互斥的,这时要读这个数据岂不麻烦?如果地址硬规定4的整数倍,可想而知,问题解决了,效率也高了。
从编程的角度来说,采用的是虚内存,与页内存存在映射关系,当然会导致上述问题。

unix出现该错误,用我的观点应该可以解释,至于楼主说的vc,如果将奇地址转化为int指针再取值的话,应该会出现问题,那为什么2倍数非4倍数取能够正确执行呢?我想,这可能和操作系统的内存映射有关,unix下可能采用的是“完全对齐”原则,而windows可能给虚内存加了一个边界,也就是说实际的虚页总比实页少至少2字节以上,目的可能是为了防止越界而导致系统崩溃(个人猜测)。

想到结构体的sizeof问题,顺带提一下,之所以>=内部变量和的大小,是否仔细看过大小的规律?这也应该是“边界”问题,可以说是编译器为了追求数据访问速度而做的一点预处理。

24,857

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 工具平台和程序库
社区管理员
  • 工具平台和程序库社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧