[ARM入门] 通过一个小实例来认识总线错误,避免采坑!

[复制链接]
 楼主| 豆杀包 发表于 2022-8-30 23:07 | 显示全部楼层 |阅读模式
什么是总线错误?

平时开发过程中,我们常遇到的、引起进程崩溃的错误,大多都是段错误吧。段错误(segmentation fault)就是指访问的内存超出了系统所给这个程序的内存空间,比如操作空指针、数组越界等。


与段错误比较相似,总线错误(Bus Error)就是因为对非对齐地址的访问导致CPU读取数据违反了一定的总线规则。


CPU处于性能方面的考虑,要求对数据进行访问时都必须是地址对齐的。如果发现进行的不是地址对齐的访问,就会发送SIGBUS信号给进程,使进程产生 core dump。


总线错误与CPU架构有关,有些架构的CPU支持不对齐访问。下面我们通过实例来进行分析:


 楼主| 豆杀包 发表于 2022-8-30 23:11 | 显示全部楼层
总线错误的实例
  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. #pragma pack(1)
  4. struct struct_x
  5. {
  6.     char a;
  7.     float b;
  8.     char c;
  9. };
  10. #pragma pack()

  11. int main(void)
  12. {
  13.     struct struct_x test = {0};

  14.     printf("sizeof(struct struct_x) = %ld\n", sizeof(test));

  15.     test.a = 1;
  16.     test.b = 2.0;
  17.     test.c = 3;

  18.     char *a = &test.a;
  19.     float *b = &test.b;
  20.     char *c = &test.c;

  21.     printf("*a = %d, addr = %p\n", *a, a);
  22.     printf("*b = %f, addr = %p\n", *b, b);
  23.     printf("*c = %d, addr = %p\n", *c, c);

  24.     return 0;
  25. }
复制代码

#pragma pack 可以改变编译器的对齐方式:
  1. #pragma pack(n)  /* 指定按n字节对齐 */
  2. #pragma pack()   /* 取消自定义字节对齐 */
复制代码


在pc端,可以正常运行:

因为x86/x64系列CPU都支持不对齐访问,也提供了开关禁用这个机制。x86/x64架构不要求对齐访问的时候,必定会有性能代价。

但是,在arm板上测试:






出现了总线错误,因为结构体变量test的成员b的地址是不对齐的地址。CPU访问地址要求是四字节对齐,访问了*(addr+0x001)就会引发异常。

这时候,在struct_x的成员a、b之前增加个占用3个字节的成员d,看看还会不会报错:
  1. struct struct_x
  2. {
  3.     char a;
  4.     char d[3];
  5.     float b;
  6.     char c;
  7. };
复制代码


可见,成员b可以正常访问,因为这时候b的地址处于四字节对齐地址。

上面的总线错误,毫无疑问,就是对齐问题导致的。

但是,这里有个疑问。假如,我们把成员b的类型改为int类型,这时候会不会产生总线错误?
  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. #pragma pack(1)
  4. struct struct_x
  5. {
  6.     char a;
  7.     int b;
  8.     char c;
  9. };
  10. #pragma pack()


  11. int main(void)
  12. {
  13.     struct struct_x test = {0};

  14.     printf("sizeof(struct struct_x) = %ld\n", sizeof(test));

  15.     test.a = 1;
  16.     test.b = 2;
  17.     test.c = 3;

  18.     char *a = &test.a;
  19.     int *b = &test.b;
  20.     char *c = &test.c;

  21.     printf("sizeof(float) = %d, sizeof(int) = %d\n", sizeof(float), sizeof(int) );
  22.     printf("*a = %d, addr = %p\n", *a, a);
  23.     printf("*b = %d, addr = %p\n", *b, b);
  24.     printf("*c = %d, addr = %p\n", *c, c);

  25.     return 0;
  26. }
复制代码


这里的int类型的b成员可以正常访问。这里的成员b的地址与我们上面发生总线错误的b的成员(float类型)的地址完全一样,float类型与int类型也都是占用4字节,但是int类型b成员却可以支持非对齐访问。

这里,暂时就认为CPU就是这么设计的吧。能解释这个问题的朋友欢迎留言讨论,谢谢!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| 豆杀包 发表于 2022-8-30 23:11 | 显示全部楼层
总结
上面的int类型的b成员虽然可以正常访问,但是我们在实际编程中,应当多注意一点,尽量要修改对齐方式。

如果确实需要,也尽量保证修改的对齐方式的代码范围尽量小,比如只针对某个结构体,并且清楚地知道有这么一回事,以至于后面加代码的时候需要非常地小心。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

49

主题

323

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部