我的机器现在是32-bits的,因此如果移位数量大于32位会出现
[-Wshift-count-overflow]的错误。
要注意的是移位运算符的优先级很低,甚至比加减法还低。
奇怪的是,我的机器在移位运算时得出的推断是32-bits的,但是在表示整数的最大值时确实以64-bits来进行的,在我的机器上一个long 型数据能够表示的最大整数是 9223372036854775807
C默认数字是有符号的。



从零到一串二进制数的最值之间,该值是和二进制数一一对应的。eg: 15 = [1,1,1,1]




补码的最小值的绝对值要比补码的最大值大1

这是因为补码的最小值一定是一个负数,而补码的最大值是一个非负数,零是非负数比如:
一串长度为4的二进制数[1 1 1 1],我们求它补码的最小值:


我们接着来求补码的最大值

求得补码的最大值是 7.
n = i + 4j
其中n为指数,0 <= i <= 3,j为十六进制0的个数。当i = 0是x开头为1,i = 1时,x开头为2,i = 2时,x开头为4。i = 3时,x开头为8。
例子:x = 2048 = 2^11,11 = 3 + 4*2,因此十六进制数字为0x800
[1 1 1 1 ] 无符号数:[0 1 1 1 ]UMax
1 + 2 + 4-----------------------Sum == 7
[1 1 1 1 ]补码(有符号数):[1 0 0 0 0 ]TMin
-8 + 0 + 0 + 0= -8------------Sum = -8
对于一串二进制数[1 1 1 1 ]它的无符号最大值是[ 0 1 1 1 ] 它的补码的最小值是[ 1 0 0 0 ]。

1. 二进制转换为补码

2. 二进制转换为无符号数

3.Unsigned 最大值

4.补码的最小值

5.补码的最大值

6.二进制转换为反码

7. 二进制转换为原码





// 浮点数不支持结合律
//
#include "stdio.h"
int main()
{
long long value = 1e10;
long long int value1 = 3.14;
printf("%lld",(value1 + value)-value);
return 0;
// result: -9223372036854775808 == 64-bits 最小long long int
//
#include "stdio.h"
// float 为小数分配23 bits而,int 占据32bits,因此转换会导致精度丢失
int main()
{
int value = 2147483647;
value = (int)(float)value;
printf("%d",value);
printf("\nHello,world");
return 0;
// result:-2147483648
#include "stdio.h"
// double 为小数分配53bits 而int 占据32bits因此不会导致精度丢失
int main()
{
int value = 2147483647;
value = (int)(double)value;
printf("%d",value);
printf("\nHello,world");
return 0;
// result:2147483647
————————————————————————————————————————————————————————————————————————————————————











计算机有哪几种不同的二进制表示形式??
char* 使用全字长
什么是掩码运算??
整数字符扩充和字符截断还需要重点关注
第二章节的习题需要做完。
int swap(int x, int y)
{
x = x^y;
y = x^y;
x = x^y;
return 1;
}
// this program has no performance advantage to this of swapping
gdb是一个非常强大的调试程序。你可以单步检查程序并对其中的程序进行一些操作。如果这个程序有对应的源代码,那么gdb会调用源代码来进行调试,不过没有也行。在gdb中可以进行反汇编。
objdump是一个重要的反汇编器。


小伙伴们大家好呀!!今天是 2022.8.28 今天继续深入理解计算机系统!!,今天学习的是:Machine level program, 主要学的是 基本的Intel x86系列汇编指令,但是并不要求会写汇编,只需要能够理解汇编就行啦!! 虽然可能理解都费劲吧

猜测: Src 是地址,也就是括号里可进行值的计算。 Dst 是最终要赋值的寄存器。搭配上移位运算简直简直了!!
long m12(long x)
{
return x*12;
}
汇编代码:
leaq (%rdi, %rdi, 2) , %rdx #%rdx = 3%rdi
salq $2, %rax #%rax = 4%rax = 4*3%rdi = 12%rdi


CF: 用于检查无符号操作数的溢出
ZF: 最近得出的结果为0
SF: 最近的操作得出的结果为负数
OF: 补码溢出
LeaL指令分明只是用于简单的算术操作。这段话挺让人困惑:
加载有效地址 (load effective address)指令leal 实际上是movl指令的变形。他的指令形式是从存储器读取数据到寄存器,但实际上他根本就没有引用存储器。它的第一个操作数看上去是一个存储器引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数。另外它还可以简洁地描述普通的算数操作。编译器经常发现一些Leal的灵活用法,根本与有效地址计算无关。目的操作数必须是一个寄存器。
从运行实体(指进程/线程/中断处理器/内核组件/)的角度来说,有效地址就是一个可以用于指定那个内存位置可以用来执行存取操作的值。·
个人理解:有效地址始终是地址,这就说明了Leal始终进行的是取址运算,而不是从指定的内存位置读取数据的运算,Leal的最终结果是将计算得到的地址交给某个寄存器也可以说leal用于生成一个指针。
Machine-Level Programming : Procedures
基于x86 硬件,
ABI:Application Binary Interface,应用程序二进制接口,用于规定寄存器的惯常用法。
call
ret
these tow just do the control part of a procedure.
%rip 程序计数器, Instruction pointer
函数的参数如何传递?
按照使用惯例,一个函数的前留个参数会使用寄存器,从第七个开始将使用栈来传递。
passing data
编译器是怎么知道如何将栈恢复到原来的位置的?
%rax 用于函数的返回值。




call 时有两个动作:1入栈call指令的下一个指令的地址用于返回。2跳转到call指令所指内容call 后有两个动作:1pop栈顶用于返回到主程序 2把栈恢复原貌so each block we use for a particular call then is called the stack frame


caller saves temporary values in its frame befor the call
callee saves temporary values in its frame before using
caller resotres them before returning to caller



相加会出错test.c:9:5: error: invalid operands to binary + (have ‘int *’ and ‘int *’)
9 | p1 += p2;
| ^~
相减会警告test.c:9:5: warning: assignment to ‘int *’ from ‘long int’ makes pointer from integer without a cast [-Wint-conversion]
9 | p1 -= p2;
| ^~
Val + i 会被缩放成 x + 4i (需要结合ppt来看此处)
#include ZLEN 5
typedef int zip_dig[ZLEN];
zip_dig cmu = {1,5,2,1,3};
zip_dig mit = {0,2,1,3,9};
zip_dig ucb = {9,4,7,2,0};
zip_dig set = {cmu, mit, ucb};
typedef定义了包含五个元素的数组,等同于封装了一个数据类型。它说明了被定义的数据必须有包含5个元素。#include ZLEN 5
#include LEN 4
typedef int zip_dig[ZLEN];
zip_dig pgh[LEN] = {
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5},
}
// 定义了一个二维数组
而当你只是声明一个指针时,你所分配的只是指针本身的空间,而没有给他指向的任何东西分配空间。
阅读K&R中关于如何读C语言中的指针和声明的那一章节
是不是把二位数组的行看成了一个指针,而把列的成为被指针指向的内容。
嵌套数组行开始地址 A + i*(C*K) 即数组首地址加上行数乘以每行所占字节数。
嵌套数组元素地址:A + i*(CK) + jK = A + (i*C + j)*K.A是数组首地址,i是行号,C是类型大小,K是每行元素个数,j 是列号比如phg[1][2], A = phg, i = 1, C = sizeof(int), K = 5, j = 2
#include "stdio.h"
#define ZLEN 5
#define LEN 4
typedef int zip_dig[ZLEN];
zip_dig phg[LEN] = {
{1,2,3,4,5},
{6,7,8,9,10},
{11,12,13,14,15},
{16,17,18,19,20}
};
int main()
{
return phg[2][1] + phg[1][1];
}
main:
.LFB23:
.cfi_startproc
endbr64
movl 24+phg(%rip), %eax # %rip + 4*5*1 + 4*i = phg(%rip) + 24, phg[1][1]
addl 44+phg(%rip), %eax # %rip + 4*5*2 + 4*1 = phg(%rip) + 48, phg[2][1]
ret
.cfi_endproc






(注:相同的数据类型如果不够对齐要求应该放到一起,当然前提是他们本身就临近。第一个字符总是不偏移。)
Canary:金丝雀
矿井工人使用金丝雀来检测矿井下的甲烷含量。
一般来说,我们使用GCC的时候都会启动栈保护机制
##它假设你只会同时使用一个域,如果你同时使用多个域,那么就会出问题。他不是用来处理多个值的,或者说他只是一种通过别名来引用不同的内存的方式。
#define int data_t
#define int size_t
typedef struct
{
int len;
data_t data[len];
}val;
int get_element(*val v,size_t idx,data_t* rtn)
{
if(idx >= v->len) return 0;
*rtn = v->data[idx];
return 1;
}
// 使用这种方式可以避免数组溢出
#define OP +
#define IDENT 0
or
#define OP *
#define IDENT 1
// 本函数用于计算一个数组中所有元素的和或者是积
void combine(val v,data_t* dest)
{
long int i;
*dest = IDENT;
int len = get_length(v);
for(i = 0; i < len;i++)
{
data_t val;
get_element(&v,i, &val);
*dest = *dest OP val;
}
}
//使用这种方式可以轻松获得更多运算:加,乘
Optimize version
data_t = IDENT;
int len= get_length(v);
data_t * d = get_array_start(v);
for (i = 0; i < len; i++)
{
t = t OP d[i];
}
*dest = t;
FP:浮点数
latency:
CPE: Cycle per Elememt
%xmm寄存器
矢量加法指令,其中一条矢量加法指令具有执行八次单精度浮点加法的效果或者四次双精度浮点加法。你可以在三个始终周期内并行进行八次浮点流水线乘法。我们最多能接近矢量吞吐量界限。
1.所谓“随机存取”,指的是当存储器中的数据被读取或写入时,所需要的时间与这段信息所在的位置或所写入的位置无关。相对的,读取或写入顺序访问(Sequential Access)存储设备中的信息时,其所需要的时间与位置就会有关系。它主要用来存放操作系统、各种应用程序、数据等。
2.当电源关闭时,RAM不能保留数据。如果需要保存数据,就必须把它们写入一个长期的存储设备中(例如硬盘)。 [3]
RAM的工作特点是通电后,随时可在任意位置单元存取数据信息,断电后内部信息也随之消失。
我的机器现在是32-bits的,因此如果移位数量大于32位会出现
[-Wshift-count-overflow]的错误。
要注意的是移位运算符的优先级很低,甚至比加减法还低。
奇怪的是,我的机器在移位运算时得出的推断是32-bits的,但是在表示整数的最大值时确实以64-bits来进行的,在我的机器上一个long 型数据能够表示的最大整数是 9223372036854775807
C默认数字是有符号的。



从零到一串二进制数的最值之间,该值是和二进制数一一对应的。eg: 15 = [1,1,1,1]




补码的最小值的绝对值要比补码的最大值大1

这是因为补码的最小值一定是一个负数,而补码的最大值是一个非负数,零是非负数比如:
一串长度为4的二进制数[1 1 1 1],我们求它补码的最小值:


我们接着来求补码的最大值

求得补码的最大值是 7.
n = i + 4j
其中n为指数,0 <= i <= 3,j为十六进制0的个数。当i = 0是x开头为1,i = 1时,x开头为2,i = 2时,x开头为4。i = 3时,x开头为8。
例子:x = 2048 = 2^11,11 = 3 + 4*2,因此十六进制数字为0x800
[1 1 1 1 ] 无符号数:[0 1 1 1 ]UMax
1 + 2 + 4-----------------------Sum == 7
[1 1 1 1 ]补码(有符号数):[1 0 0 0 0 ]TMin
-8 + 0 + 0 + 0= -8------------Sum = -8
对于一串二进制数[1 1 1 1 ]它的无符号最大值是[ 0 1 1 1 ] 它的补码的最小值是[ 1 0 0 0 ]。

1. 二进制转换为补码

2. 二进制转换为无符号数

3.Unsigned 最大值

4.补码的最小值

5.补码的最大值

6.二进制转换为反码

7. 二进制转换为原码





// 浮点数不支持结合律
//
#include "stdio.h"
int main()
{
long long value = 1e10;
long long int value1 = 3.14;
printf("%lld",(value1 + value)-value);
return 0;
// result: -9223372036854775808 == 64-bits 最小long long int
//
#include "stdio.h"
// float 为小数分配23 bits而,int 占据32bits,因此转换会导致精度丢失
int main()
{
int value = 2147483647;
value = (int)(float)value;
printf("%d",value);
printf("\nHello,world");
return 0;
// result:-2147483648
#include "stdio.h"
// double 为小数分配53bits 而int 占据32bits因此不会导致精度丢失
int main()
{
int value = 2147483647;
value = (int)(double)value;
printf("%d",value);
printf("\nHello,world");
return 0;
// result:2147483647
————————————————————————————————————————————————————————————————————————————————————











计算机有哪几种不同的二进制表示形式??
char* 使用全字长
什么是掩码运算??
整数字符扩充和字符截断还需要重点关注
第二章节的习题需要做完。
int swap(int x, int y)
{
x = x^y;
y = x^y;
x = x^y;
return 1;
}
// this program has no performance advantage to this of swapping
gdb是一个非常强大的调试程序。你可以单步检查程序并对其中的程序进行一些操作。如果这个程序有对应的源代码,那么gdb会调用源代码来进行调试,不过没有也行。在gdb中可以进行反汇编。
objdump是一个重要的反汇编器。


小伙伴们大家好呀!!今天是 2022.8.28 今天继续深入理解计算机系统!!,今天学习的是:Machine level program, 主要学的是 基本的Intel x86系列汇编指令,但是并不要求会写汇编,只需要能够理解汇编就行啦!! 虽然可能理解都费劲吧

猜测: Src 是地址,也就是括号里可进行值的计算。 Dst 是最终要赋值的寄存器。搭配上移位运算简直简直了!!
long m12(long x)
{
return x*12;
}
汇编代码:
leaq (%rdi, %rdi, 2) , %rdx #%rdx = 3%rdi
salq $2, %rax #%rax = 4%rax = 4*3%rdi = 12%rdi


CF: 用于检查无符号操作数的溢出
ZF: 最近得出的结果为0
SF: 最近的操作得出的结果为负数
OF: 补码溢出
LeaL指令分明只是用于简单的算术操作。这段话挺让人困惑:
加载有效地址 (load effective address)指令leal 实际上是movl指令的变形。他的指令形式是从存储器读取数据到寄存器,但实际上他根本就没有引用存储器。它的第一个操作数看上去是一个存储器引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数。另外它还可以简洁地描述普通的算数操作。编译器经常发现一些Leal的灵活用法,根本与有效地址计算无关。目的操作数必须是一个寄存器。
从运行实体(指进程/线程/中断处理器/内核组件/)的角度来说,有效地址就是一个可以用于指定那个内存位置可以用来执行存取操作的值。·
个人理解:有效地址始终是地址,这就说明了Leal始终进行的是取址运算,而不是从指定的内存位置读取数据的运算,Leal的最终结果是将计算得到的地址交给某个寄存器也可以说leal用于生成一个指针。
Machine-Level Programming : Procedures
基于x86 硬件,
ABI:Application Binary Interface,应用程序二进制接口,用于规定寄存器的惯常用法。
call
ret
these tow just do the control part of a procedure.
%rip 程序计数器, Instruction pointer
函数的参数如何传递?
按照使用惯例,一个函数的前留个参数会使用寄存器,从第七个开始将使用栈来传递。
passing data
编译器是怎么知道如何将栈恢复到原来的位置的?
%rax 用于函数的返回值。




call 时有两个动作:1入栈call指令的下一个指令的地址用于返回。2跳转到call指令所指内容call 后有两个动作:1pop栈顶用于返回到主程序 2把栈恢复原貌so each block we use for a particular call then is called the stack frame


caller saves temporary values in its frame befor the call
callee saves temporary values in its frame before using
caller resotres them before returning to caller



相加会出错test.c:9:5: error: invalid operands to binary + (have ‘int *’ and ‘int *’)
9 | p1 += p2;
| ^~
相减会警告test.c:9:5: warning: assignment to ‘int *’ from ‘long int’ makes pointer from integer without a cast [-Wint-conversion]
9 | p1 -= p2;
| ^~
Val + i 会被缩放成 x + 4i (需要结合ppt来看此处)
#include ZLEN 5
typedef int zip_dig[ZLEN];
zip_dig cmu = {1,5,2,1,3};
zip_dig mit = {0,2,1,3,9};
zip_dig ucb = {9,4,7,2,0};
zip_dig set = {cmu, mit, ucb};
typedef定义了包含五个元素的数组,等同于封装了一个数据类型。它说明了被定义的数据必须有包含5个元素。#include ZLEN 5
#include LEN 4
typedef int zip_dig[ZLEN];
zip_dig pgh[LEN] = {
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5},
}
// 定义了一个二维数组
而当你只是声明一个指针时,你所分配的只是指针本身的空间,而没有给他指向的任何东西分配空间。
阅读K&R中关于如何读C语言中的指针和声明的那一章节
是不是把二位数组的行看成了一个指针,而把列的成为被指针指向的内容。
嵌套数组行开始地址 A + i*(C*K) 即数组首地址加上行数乘以每行所占字节数。
嵌套数组元素地址:A + i*(CK) + jK = A + (i*C + j)*K.A是数组首地址,i是行号,C是类型大小,K是每行元素个数,j 是列号比如phg[1][2], A = phg, i = 1, C = sizeof(int), K = 5, j = 2
#include "stdio.h"
#define ZLEN 5
#define LEN 4
typedef int zip_dig[ZLEN];
zip_dig phg[LEN] = {
{1,2,3,4,5},
{6,7,8,9,10},
{11,12,13,14,15},
{16,17,18,19,20}
};
int main()
{
return phg[2][1] + phg[1][1];
}
main:
.LFB23:
.cfi_startproc
endbr64
movl 24+phg(%rip), %eax # %rip + 4*5*1 + 4*i = phg(%rip) + 24, phg[1][1]
addl 44+phg(%rip), %eax # %rip + 4*5*2 + 4*1 = phg(%rip) + 48, phg[2][1]
ret
.cfi_endproc






(注:相同的数据类型如果不够对齐要求应该放到一起,当然前提是他们本身就临近。第一个字符总是不偏移。)
Canary:金丝雀
矿井工人使用金丝雀来检测矿井下的甲烷含量。
一般来说,我们使用GCC的时候都会启动栈保护机制
##它假设你只会同时使用一个域,如果你同时使用多个域,那么就会出问题。他不是用来处理多个值的,或者说他只是一种通过别名来引用不同的内存的方式。
#define int data_t
#define int size_t
typedef struct
{
int len;
data_t data[len];
}val;
int get_element(*val v,size_t idx,data_t* rtn)
{
if(idx >= v->len) return 0;
*rtn = v->data[idx];
return 1;
}
// 使用这种方式可以避免数组溢出
#define OP +
#define IDENT 0
or
#define OP *
#define IDENT 1
// 本函数用于计算一个数组中所有元素的和或者是积
void combine(val v,data_t* dest)
{
long int i;
*dest = IDENT;
int len = get_length(v);
for(i = 0; i < len;i++)
{
data_t val;
get_element(&v,i, &val);
*dest = *dest OP val;
}
}
//使用这种方式可以轻松获得更多运算:加,乘
Optimize version
data_t = IDENT;
int len= get_length(v);
data_t * d = get_array_start(v);
for (i = 0; i < len; i++)
{
t = t OP d[i];
}
*dest = t;
FP:浮点数
latency:
CPE: Cycle per Elememt
%xmm寄存器
矢量加法指令,其中一条矢量加法指令具有执行八次单精度浮点加法的效果或者四次双精度浮点加法。你可以在三个始终周期内并行进行八次浮点流水线乘法。我们最多能接近矢量吞吐量界限。
1.所谓“随机存取”,指的是当存储器中的数据被读取或写入时,所需要的时间与这段信息所在的位置或所写入的位置无关。相对的,读取或写入顺序访问(Sequential Access)存储设备中的信息时,其所需要的时间与位置就会有关系。它主要用来存放操作系统、各种应用程序、数据等。
2.当电源关闭时,RAM不能保留数据。如果需要保存数据,就必须把它们写入一个长期的存储设备中(例如硬盘)。 [3]
RAM的工作特点是通电后,随时可在任意位置单元存取数据信息,断电后内部信息也随之消失。
随机存取存储器(英语:Random Access Memory,缩写:RAM),也叫主存,是与CPU直接交换数据的内部存储器。它可以随时读写(刷新时除外),而且速度很快,通常作为操作系统或其他正在运行中的程序的临时数据存储介质。RAM工作时可以随时从任何一个指定的地址写入(存入)或读出(取出)信息。它与ROM的最大区别是数据的易失性,即一旦断电所存储的数据将随之丢失。RAM在计算机和数字系统中用来暂时存储程序、数据和中间结果。
read/write heads move in unison from cylinder to cylinder:读/写头从一个柱面移动到另一个柱面。
why? interrupt (zhongduanjizhi)
but a page can only be written after the entire block has been erased
If you want to write to a page you have to find a block somewhere that’s been erased.
SSD : Solid State Disk
int sum_array_cols( int a[M][N])
{
int i, j, sum = 0;
for (j = 0; j < N; j++)
for (i = 0; i < M; i++)
sum += a[i][j];
return sum;
}
// 非常糟糕的例子,跳跃式访问空间
int sum_array_cols( int a[M][N])
{
int i, j, sum = 0;
for (j = 0; j < M; j++)
for (i = 0; i < N; i++)
sum += a[i][j];
return sum;
}
// 最优累加数组元素的方法
int sum_array_3d(int a [M][N][N])
{
int i,j,k, sum = 0;
for (i = 0;i < M; i++)
for(j = 0; j < N; j++)
for(k = 0; k < N; k++)
sum += a[i][j][k];
return sum;
}
/// 最优访问三位数组的方式

注意:每次拷贝新的块到缓存时都会导致其他的块被驱逐。
TLB : translation lookaside buffer是一个在虚拟内存中使用的缓存
随机存取存储器(英语:Random Access Memory,缩写:RAM),也叫主存,是与CPU直接交换数据的内部存储器。它可以随时读写(刷新时除外),而且速度很快,通常作为操作系统或其他正在运行中的程序的临时数据存储介质。RAM工作时可以随时从任何一个指定的地址写入(存入)或读出(取出)信息。它与ROM的最大区别是数据的易失性,即一旦断电所存储的数据将随之丢失。RAM在计算机和数字系统中用来暂时存储程序、数据和中间结果。
read/write heads move in unison from cylinder to cylinder:读/写头从一个柱面移动到另一个柱面。
why? interrupt (zhongduanjizhi)
but a page can only be written after the entire block has been erased
If you want to write to a page you have to find a block somewhere that’s been erased.
SSD : Solid State Disk
int sum_array_cols( int a[M][N])
{
int i, j, sum = 0;
for (j = 0; j < N; j++)
for (i = 0; i < M; i++)
sum += a[i][j];
return sum;
}
// 非常糟糕的例子,跳跃式访问空间
int sum_array_cols( int a[M][N])
{
int i, j, sum = 0;
for (j = 0; j < M; j++)
for (i = 0; i < N; i++)
sum += a[i][j];
return sum;
}
// 最优累加数组元素的方法
int sum_array_3d(int a [M][N][N])
{
int i,j,k, sum = 0;
for (i = 0;i < M; i++)
for(j = 0; j < N; j++)
for(k = 0; k < N; k++)
sum += a[i][j][k];
return sum;
}
/// 最优访问三位数组的方式
截图:内存层级结构金字塔图
[-Wshift-count-overflow]的错误。
要注意的是移位运算符的优先级很低,甚至比加减法还低。
奇怪的是,我的机器在移位运算时得出的推断是32-bits的,但是在表示整数的最大值时确实以64-bits来进行的,在我的机器上一个long 型数据能够表示的最大整数是 9223372036854775807
C默认数字是有符号的。



从零到一串二进制数的最值之间,该值是和二进制数一一对应的。eg: 15 = [1,1,1,1]




补码的最小值的绝对值要比补码的最大值大1

这是因为补码的最小值一定是一个负数,而补码的最大值是一个非负数,零是非负数比如:
一串长度为4的二进制数[1 1 1 1],我们求它补码的最小值:


我们接着来求补码的最大值

求得补码的最大值是 7.
n = i + 4j
其中n为指数,0 <= i <= 3,j为十六进制0的个数。当i = 0是x开头为1,i = 1时,x开头为2,i = 2时,x开头为4。i = 3时,x开头为8。
例子:x = 2048 = 2^11,11 = 3 + 4*2,因此十六进制数字为0x800
[1 1 1 1 ] 无符号数:[0 1 1 1 ]UMax
1 + 2 + 4-----------------------Sum == 7
[1 1 1 1 ]补码(有符号数):[1 0 0 0 0 ]TMin
-8 + 0 + 0 + 0= -8------------Sum = -8
对于一串二进制数[1 1 1 1 ]它的无符号最大值是[ 0 1 1 1 ] 它的补码的最小值是[ 1 0 0 0 ]。

1. 二进制转换为补码

2. 二进制转换为无符号数

3.Unsigned 最大值

4.补码的最小值

5.补码的最大值

6.二进制转换为反码

7. 二进制转换为原码





// 浮点数不支持结合律
//
#include "stdio.h"
int main()
{
long long value = 1e10;
long long int value1 = 3.14;
printf("%lld",(value1 + value)-value);
return 0;
// result: -9223372036854775808 == 64-bits 最小long long int
//
#include "stdio.h"
// float 为小数分配23 bits而,int 占据32bits,因此转换会导致精度丢失
int main()
{
int value = 2147483647;
value = (int)(float)value;
printf("%d",value);
printf("\nHello,world");
return 0;
// result:-2147483648
#include "stdio.h"
// double 为小数分配53bits 而int 占据32bits因此不会导致精度丢失
int main()
{
int value = 2147483647;
value = (int)(double)value;
printf("%d",value);
printf("\nHello,world");
return 0;
// result:2147483647
————————————————————————————————————————————————————————————————————————————————————











计算机有哪几种不同的二进制表示形式??
char* 使用全字长
什么是掩码运算??
整数字符扩充和字符截断还需要重点关注
第二章节的习题需要做完。
int swap(int x, int y)
{
x = x^y;
y = x^y;
x = x^y;
return 1;
}
// this program has no performance advantage to this of swapping
gdb是一个非常强大的调试程序。你可以单步检查程序并对其中的程序进行一些操作。如果这个程序有对应的源代码,那么gdb会调用源代码来进行调试,不过没有也行。在gdb中可以进行反汇编。
objdump是一个重要的反汇编器。


小伙伴们大家好呀!!今天是 2022.8.28 今天继续深入理解计算机系统!!,今天学习的是:Machine level program, 主要学的是 基本的Intel x86系列汇编指令,但是并不要求会写汇编,只需要能够理解汇编就行啦!! 虽然可能理解都费劲吧

猜测: Src 是地址,也就是括号里可进行值的计算。 Dst 是最终要赋值的寄存器。搭配上移位运算简直简直了!!
long m12(long x)
{
return x*12;
}
汇编代码:
leaq (%rdi, %rdi, 2) , %rdx #%rdx = 3%rdi
salq $2, %rax #%rax = 4%rax = 4*3%rdi = 12%rdi


CF: 用于检查无符号操作数的溢出
ZF: 最近得出的结果为0
SF: 最近的操作得出的结果为负数
OF: 补码溢出
LeaL指令分明只是用于简单的算术操作。这段话挺让人困惑:
加载有效地址 (load effective address)指令leal 实际上是movl指令的变形。他的指令形式是从存储器读取数据到寄存器,但实际上他根本就没有引用存储器。它的第一个操作数看上去是一个存储器引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数。另外它还可以简洁地描述普通的算数操作。编译器经常发现一些Leal的灵活用法,根本与有效地址计算无关。目的操作数必须是一个寄存器。
从运行实体(指进程/线程/中断处理器/内核组件/)的角度来说,有效地址就是一个可以用于指定那个内存位置可以用来执行存取操作的值。·
个人理解:有效地址始终是地址,这就说明了Leal始终进行的是取址运算,而不是从指定的内存位置读取数据的运算,Leal的最终结果是将计算得到的地址交给某个寄存器也可以说leal用于生成一个指针。
Machine-Level Programming : Procedures
基于x86 硬件,
ABI:Application Binary Interface,应用程序二进制接口,用于规定寄存器的惯常用法。
call
ret
these tow just do the control part of a procedure.
%rip 程序计数器, Instruction pointer
函数的参数如何传递?
按照使用惯例,一个函数的前留个参数会使用寄存器,从第七个开始将使用栈来传递。
passing data
编译器是怎么知道如何将栈恢复到原来的位置的?
%rax 用于函数的返回值。




call 时有两个动作:1入栈call指令的下一个指令的地址用于返回。2跳转到call指令所指内容call 后有两个动作:1pop栈顶用于返回到主程序 2把栈恢复原貌so each block we use for a particular call then is called the stack frame


caller saves temporary values in its frame befor the call
callee saves temporary values in its frame before using
caller resotres them before returning to caller



相加会出错test.c:9:5: error: invalid operands to binary + (have ‘int *’ and ‘int *’)
9 | p1 += p2;
| ^~
相减会警告test.c:9:5: warning: assignment to ‘int *’ from ‘long int’ makes pointer from integer without a cast [-Wint-conversion]
9 | p1 -= p2;
| ^~
Val + i 会被缩放成 x + 4i (需要结合ppt来看此处)
#include ZLEN 5
typedef int zip_dig[ZLEN];
zip_dig cmu = {1,5,2,1,3};
zip_dig mit = {0,2,1,3,9};
zip_dig ucb = {9,4,7,2,0};
zip_dig set = {cmu, mit, ucb};
typedef定义了包含五个元素的数组,等同于封装了一个数据类型。它说明了被定义的数据必须有包含5个元素。#include ZLEN 5
#include LEN 4
typedef int zip_dig[ZLEN];
zip_dig pgh[LEN] = {
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5},
}
// 定义了一个二维数组
而当你只是声明一个指针时,你所分配的只是指针本身的空间,而没有给他指向的任何东西分配空间。
阅读K&R中关于如何读C语言中的指针和声明的那一章节
是不是把二位数组的行看成了一个指针,而把列的成为被指针指向的内容。
嵌套数组行开始地址 A + i*(C*K) 即数组首地址加上行数乘以每行所占字节数。
嵌套数组元素地址:A + i*(CK) + jK = A + (i*C + j)*K.A是数组首地址,i是行号,C是类型大小,K是每行元素个数,j 是列号比如phg[1][2], A = phg, i = 1, C = sizeof(int), K = 5, j = 2
#include "stdio.h"
#define ZLEN 5
#define LEN 4
typedef int zip_dig[ZLEN];
zip_dig phg[LEN] = {
{1,2,3,4,5},
{6,7,8,9,10},
{11,12,13,14,15},
{16,17,18,19,20}
};
int main()
{
return phg[2][1] + phg[1][1];
}
main:
.LFB23:
.cfi_startproc
endbr64
movl 24+phg(%rip), %eax # %rip + 4*5*1 + 4*i = phg(%rip) + 24, phg[1][1]
addl 44+phg(%rip), %eax # %rip + 4*5*2 + 4*1 = phg(%rip) + 48, phg[2][1]
ret
.cfi_endproc






(注:相同的数据类型如果不够对齐要求应该放到一起,当然前提是他们本身就临近。第一个字符总是不偏移。)
Canary:金丝雀
矿井工人使用金丝雀来检测矿井下的甲烷含量。
一般来说,我们使用GCC的时候都会启动栈保护机制
##它假设你只会同时使用一个域,如果你同时使用多个域,那么就会出问题。他不是用来处理多个值的,或者说他只是一种通过别名来引用不同的内存的方式。
#define int data_t
#define int size_t
typedef struct
{
int len;
data_t data[len];
}val;
int get_element(*val v,size_t idx,data_t* rtn)
{
if(idx >= v->len) return 0;
*rtn = v->data[idx];
return 1;
}
// 使用这种方式可以避免数组溢出
#define OP +
#define IDENT 0
or
#define OP *
#define IDENT 1
// 本函数用于计算一个数组中所有元素的和或者是积
void combine(val v,data_t* dest)
{
long int i;
*dest = IDENT;
int len = get_length(v);
for(i = 0; i < len;i++)
{
data_t val;
get_element(&v,i, &val);
*dest = *dest OP val;
}
}
//使用这种方式可以轻松获得更多运算:加,乘
Optimize version
data_t = IDENT;
int len= get_length(v);
data_t * d = get_array_start(v);
for (i = 0; i < len; i++)
{
t = t OP d[i];
}
*dest = t;
FP:浮点数
latency:
CPE: Cycle per Elememt
%xmm寄存器
矢量加法指令,其中一条矢量加法指令具有执行八次单精度浮点加法的效果或者四次双精度浮点加法。你可以在三个始终周期内并行进行八次浮点流水线乘法。我们最多能接近矢量吞吐量界限。
1.所谓“随机存取”,指的是当存储器中的数据被读取或写入时,所需要的时间与这段信息所在的位置或所写入的位置无关。相对的,读取或写入顺序访问(Sequential Access)存储设备中的信息时,其所需要的时间与位置就会有关系。它主要用来存放操作系统、各种应用程序、数据等。
2.当电源关闭时,RAM不能保留数据。如果需要保存数据,就必须把它们写入一个长期的存储设备中(例如硬盘)。 [3]
RAM的工作特点是通电后,随时可在任意位置单元存取数据信息,断电后内部信息也随之消失。
随机存取存储器(英语:Random Access Memory,缩写:RAM),也叫主存,是与CPU直接交换数据的内部存储器。它可以随时读写(刷新时除外),而且速度很快,通常作为操作系统或其他正在运行中的程序的临时数据存储介质。RAM工作时可以随时从任何一个指定的地址写入(存入)或读出(取出)信息。它与ROM的最大区别是数据的易失性,即一旦断电所存储的数据将随之丢失。RAM在计算机和数字系统中用来暂时存储程序、数据和中间结果。
why? interrupt (zhongduanjizhi)
but a page can only be written after the entire block has been erased
If you want to write to a page you have to find a block somewhere that’s been erased.
SSD : Solid State Disk
int sum_array_cols( int a[M][N])
{
int i, j, sum = 0;
for (j = 0; j < N; j++)
for (i = 0; i < M; i++)
sum += a[i][j];
return sum;
}
// 非常糟糕的例子,跳跃式访问空间
int sum_array_cols( int a[M][N])
{
int i, j, sum = 0;
for (j = 0; j < M; j++)
for (i = 0; i < N; i++)
sum += a[i][j];
return sum;
}
// 最优累加数组元素的方法
int sum_array_3d(int a [M][N][N])
{
int i,j,k, sum = 0;
for (i = 0;i < M; i++)
for(j = 0; j < N; j++)
for(k = 0; k < N; k++)
sum += a[i][j][k];
return sum;
}
/// 最优访问三位数组的方式
截图:内存层级结构金字塔图
注意:每次拷贝新的块到缓存时都会导致其他的块被驱逐。
TLB : translation lookaside buffer是一个在虚拟内存中使用的缓存
注意:每次拷贝新的块到缓存时都会导致其他的块被驱逐。
TLB : translation lookaside buffer是一个在虚拟内存中使用的缓存