unix/linux "数据的对齐" "指针的对齐" .

简介:  "数据的的对齐"    以下内容节选自《Intel Architecture 32 Manual》。    字,双字,和四字在自然边界上不需要在内存中对齐。

 "数据的的对齐"
    以下内容节选自《Intel Architecture 32 Manual》。
    字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)
    无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
    一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇
    数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,
    这些指令将会产生一个通用保护异常(#GP)。双四字的自然边界是能够被16整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),
    然而,需要额外的内存总线周期来访问内存中未对齐的数据。
a) 以下在hp64机上验证出数据对齐的结果

   char  ci=1  ci的地址是1 的倍数
   short si=1  si的地址是2 的倍数
   int   ii=1  ii的地址是4 的倍数
   long  li=1  li的地址是8 的倍数
b) 以下是一个内存不对齐的例子:
    unsigned char  opc_2[]={0x40};

    op1_1=(unsigned short *)opc_2;    可以给op1_1进行任何地址引用,如果不对齐,在引用值时发生错误致命COREDUMP.
    printf("op1_1=%p/n",*op1_1);     
 如果地址刚好是2的倍数,在以上能过执行,否则COREDUMP(在引用时产生printf).

说明:char型的地址因为是1的倍数,所以可能是2的倍数,4的倍数.....他是一个随机的.
    如果刚好符合程序,如上转化为

 
“指针的对齐”问题。

CPU一般要求指针的值(内存地址)要与它的指向类型数据的尺寸相匹配。例如,2个字节的数据类型被访问的地址值为 2 的倍数,
4个字节的数据类型(如 int)被访问的地址值是 4 的倍数,等等。一个字节的数据类型(如 char 型)对其访问地址无限制(因为是 1 的倍数)。

在Intel处理器上,指针对齐这个问题不是致命的,至多占用CPU更多的时间进行指针转换,从而带来性能的下降;
但是对于其它类型的处理器来说就是致命的了:如果访问的指针不对齐,会带来运行错误。

当指针从一种类型的指针转换到另外一种类型的指针的时候,就存在着产生非对齐指针的可能性。不过,正如原文中所说的,
“一个对齐要求较高的指针类型S转换成一个对齐要求较低的指针类型D是安全的”,这种转换不会产生非对齐指针;但是,
如果是一个对齐要求较低的指针类型D转换成一个对齐要求较高的指针类型S,就有可能产生非对齐指针,这种转换就是不安全。

<<c语言参考手册>>中6.1.3节中提到,把一个对齐要求较高的指针类型S转换成一个对齐要求较低的指针类型D是安全的,
安全指的是类型D在用于读取与存储D类型对象时可以得到预期效果,后面转换回原指针类型时能够恢复原指针.

举例说明:
1) 较高的指针类型到较低的指针类型转换
 在unix上,每个int型数据占4个字节,在hp系统上,例如,十六进制表示的整数 0x1a2b3c4d 在内存中是这样存放的:

(高存储地址)
 Base Address +0 1a 
 Base Address +1 2b
 Base Address +2 3c
 Base Address +3 4d
 (低存储地址)

 如果有这样的程序:
 代码:
   int a = 0x1a2b3c4d;
   int *p = &a;
   char *q = (char *)p;
   printf("%p/n", *q);  //执行结果:000000000000001a
  
   p=NULL;
   p = (int*)q;
   printf("%p/n", *p);  //执行结果:000000001a2b3c4d
 

 则其中的指针 p 和 q 的值就是 Base Address。q 是char型指针(重要),所以 *q 的结果得到 0x1a不就是我们期待的吗?
 在这种情况下,不会得到 0x1a 以外的值,所以是也可以说是“安全”的。
 *****重要:搞清楚指针操作受指针“基类型”而不是指针“所指向的对象类型”支配 就没问题了****

 无论对于自己的程序还是系统来说。程序后面两句说明,从 q 指针能够恢复原来的 p 指针,从结果来看也能得到我们预期的值。

1) 较低的指针类型到较高的指针类型转换
把一个对齐要求较低的指针类型D转换成一个对齐要求较高的指针类型S是不安全的,得不到预期值。例如,char* 到 int*的转换:
代码:
  char c = 0x1a;
  char* p = &c;
  int* q = (int*)p;

  printf("%p/n", *q);  //执行结果:000000001a000000
  p=NULL;
  p = (char*)q;
  printf("%p/n", *p);  //执行结果:000000000000001a
 

你无法预测(预期)打印出的 *q 的值是什么,因为我们除了知道整数的一个低位字节(0x1a)之外,对于这个字节后面的其余三个字节位一无所知,
其值是不确定的。因此,这样的指针转换就不是安全的(更严重的情况是用 *q 写数据,会破坏掉 0x1a 后面的三个字节的数据,给程序带来错误隐患),
其结果也是不能预测的。通过 q 恢复原来的指针 p 没有问题。

另外说明:
  对于char型数组可以自然对其
   例如: char[9],地址是8的倍数   可以把它的值赋给LONG型数据。
          char[5],地址是4的倍数
          char[3],地址是2的倍数


 


相关文章
|
1月前
|
C语言
指向指针数据的指针变量
指向指针数据的指针变量
10 0
|
11天前
|
存储 搜索推荐 C语言
运用指针轻松实现多个数据排列
这段内容介绍了一种使用PLC编程进行数据排序的方法,特别是通过冒泡排序算法来实现。文中以一个包含5个数字(90, 21, 132, -58, 34)的序列为例,详细解释了排序过程,包括多轮比较和数据交换。在每一轮中,最大的数字会移动到右侧。通过嵌套的FOR循环结构实现程序逻辑,其中外层循环控制轮次,内层循环处理每轮的比较和交换。最终,程序能够将序列从小到大排序。
24 6
|
1月前
|
分布式计算 Hadoop Java
Flink CDC产品常见问题之tidb cdc 数据量大了就疯狂报空指针如何解决
Flink CDC(Change Data Capture)是一个基于Apache Flink的实时数据变更捕获库,用于实现数据库的实时同步和变更流的处理;在本汇总中,我们组织了关于Flink CDC产品在实践中用户经常提出的问题及其解答,目的是辅助用户更好地理解和应用这一技术,优化实时数据处理流程。
|
6月前
|
存储 Linux 编译器
Linux系统中指针的详细分析与操作
Linux系统中指针的详细分析与操作
72 1
|
4月前
指向结构体类型数据的指针
指向结构体类型数据的指针。
29 3
|
5月前
【指针的进阶(3)】回调函数和qsort排序各种类型的数据
【指针的进阶(3)】回调函数和qsort排序各种类型的数据
34 0
|
5月前
|
机器学习/深度学习 存储 人工智能
【网安AIGC专题11.8】论文15 ChatGPT在软件工程中的全面作用:程序语法(AST生成、表达式匹配) 静态行为、动态分析(数据依赖和污点分析、指针分析) 提示设计(角色提示、指令提示)
【网安AIGC专题11.8】论文15 ChatGPT在软件工程中的全面作用:程序语法(AST生成、表达式匹配) 静态行为、动态分析(数据依赖和污点分析、指针分析) 提示设计(角色提示、指令提示)
74 0
|
9月前
|
存储
学C的第二十三天【继续深度剖析数据在内存中的存储:3. 浮点型在内存中的存储(重点);练习:1. 有序序列判断;2. 获得月份天数(多组输入);3. 使用指针打印数组内容;4. 使用指针使字符串逆序】-2
(4). 取出内存中的 指数E(三种情况):E全为1 指数E 是通过 真实值+中间值 算出来的,如果E全是1,(32位系统)说明E的真实值是 128,指数是128说明这个值是非常大的。 这时,如果 有效数字M 全为0,表示 ±无穷大(正负取决于符号位s)