CSAPP阅读笔记-程序的机器级表示--数组与异质结构

程序的机器级表示--数组与异质结构

数组

数组是一种将标量数据聚集成更大数据类型的方式。实现方式是产生一个指向数组元素的指针,并对这些指针进行运算。

基本原则

数组:

`T A[n]`
  • 首先:他在内存中分配一个L·N字节的连续区域。L是T类型的大小。
  • 其次:引入标识符A,作为指向数组开头的指针,这个指针的值就是数组起始位置X。
  • 可以用0 ~ N-1的整数索引来访问该数组元素。数组元素i会被存放在地址为X + L·i的地方
  • 指令movl (%rdx, %rcx, 4), %eax会执行地址计算X + 4i,读A[4]这个位置内存的值,并把结果放在寄存器%eax中。
  • 伸缩因子1、2、4、8覆盖了所有基本简单的数据类型大小。例如char型数组char B[n]就是12单字节元素组成,伸缩因子为1。

指针运算

  • C允许指针进行计算,并根据数据类型进行伸缩。例如,数组int A[4];A + i就会转为A + 4i
  • &用来产生指针。例如:&A[i]等于A + i
  • *用来间接引用指针。例如:A[i]等于* (A + i)
  • 计算同一个数据结构类型的指针之差,结果的数据类型为long,值等于两个地址之差除以该数据类型的大小。

假设int型数组E的起始位置与索引放在%rdx%rcx中,结果存放在%eax中,则:

表达式 类型 汇编
E int* X movq %rdx, %rax
E[0] int M[X] movl (%rdx), %eax
E[i] int M[X + 4i] movl (%rdx, %rcx, 4), %eax
&E[2] int* X + 8 leaq 8(%rdx), %rax
E + i - 1 int* X + 4i -4 leaq -4(%rdx, %rcx, 4), %rax
*(E + i -3) int M[X + 4i - 12] movl -12(%rdx, %rcx, 4), %eax
&E[i] - E long i movq %rcx, %rax

伸缩因子根据数据类型进行变换,指针的大小都是8字节。

数组嵌套

数组分配和引用的规则对多维数组也生效。

多维数组int A[5][3]等价于:

typedef int row3_t[3];
row3_t A[5];

元素D[i][j]的地址为:

&D[i][j] = X +L(C·i + j)

其中,X为数组的起始地址,C为数组列数。以上述例子A[5][3]举例:

元素 地址
A[0] A[0][0] X
A[0][1] X + 4
A[0][2] X + 8
A[1] A[1][0] X + 12
A[1][1] X + 16
A[1][2] X + 20
A[2] A[2][0] X + 24
A[2][1] X + 28
A[2][2] X + 32
A[3] A[3][0] X + 36
A[3][1] X + 40
A[3][2] X + 44
A[4] A[4][0] X + 48
A[4][1] X + 52
A[4][2] X + 56

异质结构

结构

定义:将不同类型的对象聚合为一个对象,用名字来区分结构的各个部分。

存储:结构的所有组成部分都存放在内存中一段连续的区域,而指向结构的指针就是结构第一个字节的地址。编译器以便宜作为内存中引用指令中的位移,从而产生对结构元素的引用。

声明与引用:

//定义
struct newStruct {
  char a;
  int b;
  ...
};
//声明
struct newStruct A;
//引用
A.a = 1;
A.b = 'a';
//即声明又引用
struct newStruct A = {1, 'a'};

// 结构指针
// 指向结构的指针从一个地方传递到另一个地方,而不是复制他们。
// 结构指针的声明
struct newStruct B = &A;
//结构指针的引用
B->a = 1;//或 *(B).a,必须要加括号,因为.的优先级高于*,会被翻译成*(B.a)

举个例子:

struct example{
    int i;
    int j;
    int a[2];
    int *p;
};

内存中的相对位置如下

       0       4       8                       16      24
偏移内容|   i   |   j   |   a[0]    |   a[1]    |   p   |

将结构体变量r,r->i内容复制到r->j上汇编如下:

Registers: r in %rdi
movl    (%rdi), %eax    Get r->i
movl    %eax, 4(%rdi)   Store in r->j
//产生指向结构内部对象的指针,只需将结构地址加上偏移量
leaq    8(%rdi, %rsi, 4), %rax  Set%rax to &r->a[i]

结构的各个字段的选取完全是在编译时处理的。

联合

定义:以多种类型来引用同一个对象。以不同的字段引用同一块内存。

struct S3{
    char c;
    int i[2];
    double v;
};

union U3{
    char c;
    int i[2];
    double v;
};

字段偏移如下:

类型 c i v 大小
S3 0 4 16 24
U3 0 0 0 8

可以看到不同与结构体,联合的总的大小等于它最大字段的大小。

联合绕过了C语言的安全措施,在实现知道数据结构互斥的场景非常有用,可以节省分配的空间总量,但是可能引起一些讨厌的错误。

联合可以用来访问不同数据类型的位模式。例如:

//将double型数据转换位unsigned long型数据 
unsigned long u = (unsigned long) d;
//使用以上强制转换会丢失精度之外还会使位表示发生变化
unsigned long double2bits(double d){
    union{
        double d;
        unsigned long u;
    }temp;
    temp.d = d;
    return temp.u;
}
//使用以上转换就会使u与d的位表示一摸一样

但在字节顺序问题变得重要的场景,联合就得小心使用。

double uu2double(unsigned word0, unsigned word1){
    union{
        double d;
        unsigned u[2];
    }temp;
    
    temp.u[0] = word0;
    temp.u[1] = word1;
    return temp.d;
}

在小端法机器上,参数word0是d的地位4个字节,而word1是高位4个字节。在大端法机器上则恰好相反。

对齐

定义:要求类型对象的地址必须是K(通常是2、4、8)的倍数。

优点:简化了形成处理器和内存系统之间的接口设计,但无论对不对齐,都不影响硬件的正确工作。

汇编指令:.align 8

举个例子:

struct  S1{
    int i;
    char c;
    int j;
};

假设是4字节对齐,画出的图来是这样的

//假设是9字节的话,如下:
        0      4       5       9
偏移内容 |  i   |   c   |   j   |
//但是9字节无法满足4的倍数,所以需要插入间隙
        0      4       5   8       12
偏移内容 |  i   |   c   |   |   j   |

强制对齐:

某些型号的Intel和AMD处理器对实现多媒体操作的SSE指令需要强制对齐16字节,否则无法正确的执行。不满足对齐要求的地址访问都会导致异常而强制终止程序。

- 任何分配函数(alloca、malloc、calloc或realloc)生成的块的起始地址都必须是16的倍数
- 大多数的函数的栈帧边界都必须是16字节的倍数。
- AVx指令是SSE指令的超集,但是没有强制性的对齐要求。

你可能感兴趣的:(CSAPP阅读笔记-程序的机器级表示--数组与异质结构)