工作中与处理器有关的问题总结

经常遇到的典型问题或者说我能想到的有以下这些:
Ø 字节序
Ø 字节对齐
Ø 异常
Ø 符号位问题
Ø 堆栈溢出
Ø 空指针
Ø 编译 & 反汇编

字节序问题: 大端法小端法
其实说白了就是一个顺序问题。现代的计算机系统 一般采用 字节 (Octet, 8 bit Byte) 作为逻辑寻址单位。 当物理单位的长度大于 1 个字节时,就要区分 字节顺序 常见的字节顺序有两种: Big Endian Little Endian. Intel X86 平台采用 Little Endian ,而 PowerPC ARM MIPS 处理器则采用了 Big Endian
           于是问题来了,一个两字节的数 0XABCD ,要存 储在 0-1 这两个字节中,那么 0 中是存 AB 还是 CD PPC ARM MIPS 等选择了 AB X86 选择了 CD.
           说到字节序,一定要区分主机序和网络序。
网络序: TCP/IP 各层协议将字节序定义为 Big- Endian ,因此 TCP/IP 协议中使用的字节序通常称之为 网络字节序。
主机序:它遵循 Little-Endian 规则。所以当两台主 机之间要通过 TCP/IP 协议进行通信的时候就需要调用 相应的函数进行主机序( Little-Endian )和网络序( Big-Endian )的转换。

编程规范里面提到过数据结构设计需要考虑字节对齐。
           所谓字节对齐,就是要求某个数据在内存中的起 始位置必须是该数据类型的 对齐大小 的整数倍。基本 数据类型( char int long 等)的对齐大小等同于类 型的大小,结构体的对齐大小等同于其各成员变量的 对齐大小的最大值,数组的对齐大小等于其基本类型 的对齐大小。数据的大小必须是对齐大小的整数倍。
为什么要字节对齐?
            因为某些处理器不允许 16 位和 32 位的数据在内存 中任意排放。
通常 32 位的处理器通过总线访问(包括读和写) 内存数据。每个总线访问周期可以访问 32 位内存数据 。内存数据是以 8 位的字节为单位存放的。假如一个 32 位的数据没有在 4 字节整除的内存地址处存放,那么处 理器就需要 2 个总线周期对其进行访问。通过合理的内 存对齐可以提高访问效率。
为什么要字节对齐?(续一)
           访存是通过特定的指令完成的,为了减少访存的 时间,大部分 CPU 都有一次可以读写 N 个字节( N 取决 于位宽)的指令,但是由于硬件上的限制,这些指令 都是有限制的,比如地址必须是 N 的整数倍(原因没 查到)。
           在不对齐的情况下,有些处理器会通过组合指令 的方式达到目标。比如需要从地址 1 处取一个 unsigned int 类型数据,可以先从 0 处取 4 字节,然后从 4 处取 4 节,再进行移位相或获得需要的数。如果是一个 int 型,则更加麻烦,因为移位的时候需要处理符号位, 对效率会有很大影响。
为什么要字节对齐?(续二)
           mips 是定长指令的设计,每条指令都是 32 比特( 或许会有 64 的出现?)。
           32 位的指令长度也就意味着要传递一个 32 位的地 址起码需要两条指令(指令有若干位是操作码)。因 此如果在 mips 中如果需要实现不对齐的内存访问,需 要耗费更多的时间。于是 mips 干脆直接不处理这种情 况,遇到不对齐的直接 error
无论如何,为了提高程序的性能,数据结构(尤其 是栈)应该尽可能地在自然边界上对齐。原因在于, 为了访问未对齐的内存,处理器需要作两次内存访问
然而,对齐的内存访问仅需要一次访问。
一个字或双字操作数跨越了 4 字节边界,或者一个 四字操作数跨越了 8 字节边界,被认为是未对齐的,从 而需要两次总线周期来访问内存。一个字起始地址是 奇数但却没有跨越字边界被认为是对齐的,能够在一 个总线周期中被访问。
编译器对内存对齐的处理: c 编译器默认将结构、 栈中的成员数据进行内存对齐。编译器将未对齐的成 员向后移,将每一个都成员对齐到自然边界上,从而 也导致了整个结构的尺寸变大。
字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量( offset )都是成员大小的整数倍,
如有需要编译器会在成员之间加上填充字节( internal adding );
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最
末一个成员之后加上填充字节( trailing padding

           不同的 CPU 对异常的定义是不一样的,比如这段代码: int *piTest = (int*)3; *piTest = 1; 这段代码 PPC 能正常处理, MIPS 则会抛出异常。另外还有一些修改 data 段的操作,在 PPC 上是 没问题的, X86 上会异常。
           异常一般通过中断处理,当 CPU 断定产生了异常的时候, 会产生一个中断,然后跳转到相应的中断向量去执行中断处理 。。
           不同的 CPU 对异常的定义不一样,编码怎么办?按照最严 格的那个来。比如字节对齐,看起来较真了一点,但是这个在 那些容忍不对齐的设备上也是有好处的,可以提高效率。而修 data 段,本身就是一个不符合常理的操作。

符号位的问题可以肯定的是这个和 mips 的一个很重要 的特性有关: mips 只能处理 32 位的数据。也即如果对 两个 char 相加, mips 需要把两个 char 填充成两个 32 的数,并对符号位进行处理,以保存溢出等特性,然 后再相加。
            这个也要求大家在定义数据的时候,需要考虑好 ,这个数据的特性,是有符号的还是无符号的

从物理上讲,堆栈就是一段连续分配的内存空间。在 一个程序中,会声明各种变量。静态全局变量是位于 数据段并且在程序开始运行的时候被加载。而程序的 动态的局部变量则分配在堆栈里面。
堆栈溢出就是不顾堆栈中分配的局部数据块大小, 向该数据块写入了过多的数据,导致数据越界。结果 覆盖了老的堆栈数据。
有两种情况会引起堆栈溢出,第一种可能出现的情 况是如果你定义了大数组会导致堆栈溢出;第二种因 为函数的参数和里面声明的局部变量,
都是在栈内分配空间。所以如果递归调用层次过 深的话,就有可能栈溢出。不要在递归函数内 申请大的空间。
           这个章节一般不会用到,但是如果能理解对提高问题处理能力还是有帮 助的。这个是编译模拟器的 .a 时的截图:
          
           红色部分大家应该能注意到这个名称包含了一个 pentium ,实际就是 奔腾处理器的名称。从这个可以看出,编译器是直接和处理器相关的,不同 的处理器会有不同的编译器。不知道谁看过《疯狂的程序员》,里面就提到 一个汇编的问题,说懂得汇编的人有个好处,不用等那些最新的 CPU 的编译 器,因为厂商推出一款新的架构的处理器的时候,有可能会先推出汇编编译 程序,其他语言的编译器可能需要过一阵子才能发布。之所以这样是因为不 同的 CPU 的指令集是不一样的,而汇编基本是和指令集挂钩的,因为汇编解 析程序实现比较容易,而像 C 这种高级语言的解析则费劲的多。
           编译中大部分的错误提示都是语言性质的提示,处理器性质的提示很少
           编译过程我们不用太多关注处理器的信息,那是编译器的 任务。但是反汇编的时候,如果需要理解反汇编出来的代码, 则必须要理解相关处理器的指令集。我认为应该多少学习一下 汇编代码。
           而我们为什么要会反汇编。我在 2126EA-MA(B) 这款 MIPS 芯片的设备中深刻体会了一把,也吃到了不少甜头。个人觉得 反汇编有两个作用: 1 debug 。有些看代码很难找的 bug 在反 汇编下很容易 原形毕露 2 、更好的理解你的代码。反汇编出 来的东西更接近指令,对比阅读能让你更好的理解同一个功能 ,用不同的代码写会有什么不同的指令体现,什么方式效率更 高。这也是我上面提到的大家最好抽空学学汇编语言,有助于 理解代码,更好更快的定位问题。

以下是个人的一些对处理器、编译器的理解后总结的编 程习惯:
(一) 不要有太多的条件分支。这个与处理器的流水 线技术有关。一条指令的执行一般会分成几个步骤, 比如取指、译指、执行指令等,很多处理器有 7-20 左右的步骤,每条指令都需要顺序经过每个步骤。另 外指令是需要依次执行的。以三个步骤为例说明流水 线技术。每个步骤都需要特定的硬件模块执行,比如 取指需要取指模块完成,译指需要译指模块完成。假 设每个步骤耗时为 t ,如果处理器只有执行结束一条指 令的所有步骤之后才执行下一条指令,那么三条指令 A B C )总共需要 9t 时间。如果采用流水线技术 ,那么当 A 进入译指模块的时候,取指模块已经取出 B
的指令了,当 A 进入执行阶段的时候, B 进入译指阶段, C 进入取指阶段。这样下来三条指令只需花费 5t 的时间,当指 令很多的时候,每条指令花费的时间应该是 t 。当然这个是理想情况,导致流水线中断的有多个原因,比如数据依赖 和条件分支,这里说一下条件分支。在条件分支下, A 的下一条指令可能不是 B 而是 E 。如果 A 执行完成之后,发现 下一条指令是 E 而不是 B ,这个时候, B 已经完成了译指, C 完成了取指。但是不但白做了,还增加了额外的负担: 清空流水线,这个对效率的影响是很明显的。因此对于那些对效率要求很高的算
法而言最好减少条件分支。 Gcc 提供了一些属性,用于 if 语句中判断这个 if 是有可能还是不大可能。比如内 存分配失败不大可能出现,可以写成 if (unlikely(NULL == p)) 表示 p==NULL 出现的概率很小,那么编译器就 会优化这段代码,让流水线的下一条指令是 if 外面的代 码。对于那些可能性很大的代码,则写成 if (likely(NULL != p)) 则流水线的下一条指令是 if 内部的代
(二) 如果内存不成问题或者有其他原因,尽可能用等 同于处理器位宽的类型。这个对于 mips CPU 来说尤 为关键,一来影响访存,而来影响运算。从 PPC 的反 汇编来看,如果是两个 ushort_t 进行逻辑运算也需要扩 充成 32 位的进行,扩充的过程是需要浪费时间的。
(三) 能有 ++i 的地方最好别用 i++ 。当然这个大部分的 编译器可能会优化掉。 i++ 的过程比 ++i 要复杂多了, i++ ,需要先备份一下 i ,然后加 1 ,再执行相应的操作
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值