C语言杂记 -- 简陋的<深入理解计算机系统>笔记

程序的表示

l 32位64位操作系统是由CPU寄存器的位数决定,即虚拟寻址的范围为2^32、2^64。

l 字节的大端小端法是以字节为基本单位的:比如十进制的7在十六位机器上表示

·














地址

100

101

大端法

00000000

00000111

小端法

00000111

00000000

l 大部分编译器默认进行算数右移和用补码表示复数

l 对于移位k来说,实际移位量为k mod 2^w(w为所移动数据类型的位数)

l 对于整型常量来说c编译器都是先将其看成正数,如果前面有 - 运算符则对其去负即转换成补码表示。

l //这造成了C中及其隐晦的存在

l //limits.h

l #define INT_MAX 2147483647   // 二进制表示方法为0111 1111 1111
1111,为C最大表示的整型数2^31
- 1

l #define INT_MIN (-INT_MAX - 1)

l /*

l 为什么不直接写成-2147483648呢? 这是因为编译器在看到这个常量时,不会先看取负元算符,而是看到了2147483648即2^31,编译器会感觉到无法用int型来表示,便会将其用长整形来表示为unsigned
int 1000 0000 0000 0000,这样它的类型就为unsigned,在你进行使用的时候便会发生错误,考虑如下代码

l */

l #include
<limits.h>

l #include
<stdio.h>

l #include
<stdlib.h>

l

l int main()

l {

l     int
a = 5;

l

l     printf("%d",
a > -2147483648);
// result is 0,
这是因为比较过程中发生了隐式转换,转换成了unsigned
int 之间的比较。

l     printf("%d",
a > INT_MIN);     //
result is 1,两者都是int类型,所以比较成功

l     return 0;

l }

这就是C语言中,负数最小值为-2147483648,却不能直接写出的原因。具体请参考http://www.cnblogs.com/Jack47/archive/2013/01/06/TMin32-in-c.html

l 浮点数:

n 32位浮点数由一位符号位s,8位阶码exp即权值,23位尾数



























数值类型

s

exp

尾数f

规格化

s

!=0 && != 255

f(尾数定义为1+f), 阶码为exp
- 01111111

非规格化

s

0000 0000

尾数为f(尾数定义为f,
exp为1 - 01111111)原因见下

无穷大

s(1,0分别表示正负无穷大)

1111 1111

000000000000000.......

NaN(NOT a NUMBER)

s

1111 1111

!=0

n 原因保证最小规格化数到最大规格化数的平稳过渡,这样最小规格化数的exp为0000 0001 - 0111 1111 = -126;最大非规格化数的exp为1 - 0111 1111 = -126. 这样就保证了二进制小数数值均匀的接近于零

n 64位浮点数一位符号位s, 11位阶码, 52位尾数, 原理相同.

n 浮点数的舍入(多采用向偶数舍入, 又称其为向最接近的值舍入): 当值不位于要舍入的中间值时, 向最近的整数舍入; 当值为中间值时, 向最接近的偶数舍入. 比如1.5舍入为整数, 因为其位于一和二中间, 向偶数舍入, 为2.

汇编指令(ATT格式)

l 知识准备:32位系统寄存器






































名称

特殊作用


%eax

通常作为函数返回值,

低十六位组成%ax, %ax中高八位为%ah,低八位为%al

%ecx


同上

%edx


同上

%ebx


同上

%esi


只有低十六位的

%edi


同上

%esp

栈指针

同上

%ebp

帧指针

同上

l 操作数(R表示取寄存器的值, M(addr)表示对地址addr的引用

























































类型

格式

操作数值

例子

立即数

$imm

imm

push $imm: 入栈imm

寄存器

%e

R[e]

push %eax: 寄存器%eax的值入栈

存储器

(imm)

M[imm]

push (4): 地址4处的值入栈

存储器

(%e)

M[R[e]]

push (%eax): 以%eax的值为地址的值入栈

存储器

imm(%e)

M[R[e] + imm]

...

存储器

(Ea, Eb)

M[R[Ea]+ R[Eb]]

...

存储器

imm(Ea, Eb)

M[R[Ea]+ R[Eb] + imm]

...

存储器

(, Ei, s)

M[R[Ei] * s]

...

存储器

(Eb, Ei, s)

M[R[Eb]+ R[Ei] * s]

...

存储器

imm(Eb, Ei, s)

M[R[Eb]+ R[Ei] * s + imm]

最常用

l 数据传送指令:MOV(S, Z), S和D不能同时为存储器目标

n Mov[b,
w, l]:分别传送[字节, 字,
双字],

n Movb
S, D: 将S传送到D

n Movs[bw,
bl, wl]: 符号扩展

n Movz[bw,
bl, wl]: 零扩展

n Pushl:
双字入栈, eg, push %eax

n Popl:
双字出栈, eg, pop %eax

l 算术和逻辑操作

n Leal
S, D: &SàD

n INC
D: D++

n DEC
D: D--

n DEG
D: -D

n NOT
D: ~D (取补)

n ADD
S, D: D = D + S

n SUB
S, D: D = D - S

n IMUL
S, D: D = S * D

n XOR
S, D: D = D ^ S (异或)

n OR
S, D: D = D | S

n AND
S, D: D = D & S

n SAL(SHL)
k, D: D = D << k

n SAR
k, D: D = D >> k (算数右移)

n SHR
k, D: D = D >> k (逻辑右移)

l 特殊的算数操作

n imull
S: 某个寄存器 = S * R[%eax] (有符号)

n mull
S: (无符号)

n cltd
S: R[%eax] 进行符号扩展到某个寄存器

n idivl
S: 有符号除法, R[%eax] / S; 商放到%eax中, 余数放到%edx中

n divl
S: 无符号除法

l 控制:条件,
循环

n 条件(根据条件码来设置)

u cmp[b,
w, l]: 分别比较字节, 字,
双字.   cmp S1 S2(基于
S2 - S1), 会设置条件码

u set[e(equal),
n(not), s(负数),  g(great), l(less),
a(above), b(below)] D: 根据条件码集合将D设置为1或0.

u test
S1, S2(基于S1 & S2, 只改变条件码,
通常用来测试S1 ?= S2)

u j[…与set相同,
额外有mp]: jmp LABLE(直接跳转到LABLE),
jmp *op(间接跳转到对op解引用后,
以解引用后的值进行跳转)

u 条件,循环控制多借助于上述组合来使用:eg

test
%edx, %eax

je
.L3   如果相等则跳转到.L3

u .L3

u     伪代码

cmp
%edx, %eax

jne
.L3   如果不相等则跳转到.L3

u .L3

u     伪代码

n 条件传送指令: 有利于现代处理器更好的执行, 避免分支惩罚

u 类似于 x < y ? y - x : x - y, 语句可以产生条件传送指令

u 配合上文的条件码, cmov[与set相同],
cmovl S, R 若小于则传送

n switch语句配合跳转表来使用, 只进行一次条件判断

n call
Label(* Operand): 过程调用, 将返回地址入栈,
并跳转到被调用过程的起始地址

n ret:
与call指令作用相反

优化程序性能

l 消除循环的低效率

n 对于循环中的过程调用尽量移出循环外, 例如:

for
(i =
0; i < strlen(s); i++)
//strlen()
函数为线性增长,在字符串长度很大时,很消耗系统资源

n 减少不必要的存储器引用, 将存储器引用储存在临时变量中.

l 处理器优化: 即充分利用存储器流水线操作的吞吐量

n 循环展开, 减少读写相关, 即所使用的数据必须等待上一次操作完成.

n 重新结合变换, 减少读写相关, eg

for
()

acc
=
(acc OP
data[i]) OP
data[i +
1]

// acc = acc
OP (data[i] OP data[ i + 1]    相比上一行,
处理器可以更新acc的同时进行(data[i]
OP data[i + 1]), 而上一行就必须等待acc更新完之后才能进行(acc
OP data[i])操作.

l 利用局部性原理: 高速缓存

n 时间局部性: 被引用过一次的存储器位置可能在不远的时间内继续被引用

n 空间局部性: 如果一个存储器位置被引用了, 很可能在不远的将来被引用其附近的存储器位置

n 每一级缓存只是关心其上下级缓存的分组情况, 缓存管理可能是硬件本身,
或者软件, 亦或是二者的结合

l 内核为每个进程维护一个描述符表, 描述符表(每个进程独有)是一个结构指针数组, 每个指针指向打开文件表, 打开文件表(包含当前文件位置, 引用计数等, 所有进程共享)包含一个指向v_node表的指针, v_node表也是包含一些文件基本信息(所有进程共享). 值得注意的是, 内核默认为每个进程打开标准输出, 标准输入, 标准错误输出三个文件, 如果任何一个打开文件表的引用计数变为零, 内核则会收回打开文件表中此文件的内存,
在对其引用可能会发生意想不到的错误. 子进程与父进程不同且继承的是描述符表,
同时系统借助描述符表和虚拟存储器来实现文件共享.

时间: 2025-01-02 18:29:31

C语言杂记 -- 简陋的<深入理解计算机系统>笔记的相关文章

深入理解计算机系统笔记

我的博客上的比这个排版显示的更好一些,特别是图片 http://notelzg.github.io/2016/06/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/ 1. hello wordl 我们还是从hello world程序说起吧: #include <stdio.h> int main() { printf("hell

深入理解计算机系统(2.5)------C语言中的有符号数和无符号数以及扩展和截断数字

上一篇博客我们讲解了计算机中整数的表示,包括无符号编码和补码编码,以及它们之间的互相转换,个人觉得那是非常重要的知识要点.这篇博客我们将介绍C语言中的有符号数和无符号数以及扩展和截断数字. 1.C语言中的有符号数和无符号数 上一篇博客我们给出了C语言中在32位机器和64位机器中支持的整型类型数据,我们这里只给出32位机器上的: 尽管 C 语言标准没有指定有符号数要采用某种编码表示,但是几乎所有的机器都使用补码.通常大多数数字是默认有符号的,比如当声明一个像12345或者0xABC这样的常量的时候

深入理解计算机系统9个重点笔记

引言 深入理解计算机系统,对我来说是部大块头.说实话,我没有从头到尾完完整整的全部看完,而是选择性的看了一些我自认为重要的或感兴趣的章节,也从中获益良多,看清楚了计算机系统的一些本质东西或原理性的内容,这对每个想要深入学习编程的程序员来说都是至关重要的.只有很好的理解了系统到底是如何运行我们代码的,我们才能针对系统的特点写出高质量.高效率的代码来.这本书我以后还需要多研究几遍,今天就先总结下书中我已学到的几点知识. 重点笔记 编写高效的程序需要下面几类活动: 选择一组合适的算法和数据结构.这是很

《深入理解计算机系统》 Chapter 7 读书笔记

<深入理解计算机系统>Chapter 7 读书笔记 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(货被拷贝)到存储器并执行. 链接的时机 编译时,也就是在源代码被翻译成机器代码时 加载时,也就是在程序被加载器加载到存储器并执行时 运行时,由应用程序执行 链接器使分离编译称为可能. 一.编译器驱动程序 大部分编译系统提供编译驱动程序:代表用户在需要时调用语言预处理器.编译器.汇编器和链接器. 1.将示例程序从ASCⅡ码源文件翻译成可执行目标文件的步骤 (1)运

【转】《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误

原文地址:http://blog.csdn.net/slvher/article/details/9150597 对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的模块跑起来后才出现内存崩溃,是很让人痛苦的.因为崩溃的位置在时间和空间上,通常是在距真正的错误源一段距离之后才表现出来.前几天线上模块因堆内存写越界1个字节引起各种诡异崩溃,定位问题过程中的折腾仍历历在目,今天读到<深入理解计算机系统>第9章-虚拟存储器,发现书中总结了C程序中常见的内存操作有

深入理解计算机系统(4.2)---硬件的魅力

引言 这个系列已经很久没更新了,记得上一篇博文已经是三月份了,实在是抱歉.最近业余时间没有以前充裕了,因此更新一篇博文已经变成了一种奢侈.记得以前刚开始写的时候,最多的时候LZ一天写过3篇博文,现在想想,往事如梦. 好了,好不容易写一次,就不多说废话了,本文主要介绍一下硬件以及HCL语言的内容. 从疑问开始 首先,在介绍本文的内容之前,我们先来思考一个看似简单,却实际比较“高深”的问题.众所周知,计算机归根结底是在和0.1打交道,那么到底0和1是如何被计算机记住的呢? 怎么样,这个问题是不是有点

深入理解计算机系统读书笔记一 ---&gt; 计算机基础漫游

一.程序编译的不同阶段. 通常我们是以高级程序开发易于阅读的代码,我们通过语法规则推断代码的具体含义.但是计算机执行代码的时候就需要把代码解析成既定的可执行问题,计算机是如何处理的呢?这里以C语言hello.c文件为例来说明中间过程. #include <stdio.h> int main() { printf("hello world!\n"); } 先上张图. C语言源程序----预处理解析头文件和函数  --- 编译器解析成汇编语言 ---   翻译机器语言指令,打包

深入理解计算机系统(3)

深入理解计算机系统(3) 本文我们主要讲关于数据的的表示方式:原码,反码和补码. 本文在写作过程中,参考了园中的这篇文章<原码,反码,补码详解>,特此声明. 一原码 计算机中是使用二进制来表示数据的,对于C语言这样的强类型语言,每一个数值类型,都有其范围,例如一个int类型,在32位机器上,其表示的范围如下: 最小值 最大值 有符号整数 -2147483648 217483647 无符号整数 0 4294967295 而如果我们定义了一个int类型,给其的赋值,超出了这个范围,则会出现问题.一

20135302魏静静——《深入理解计算机系统》第7章 学习笔记

<深入理解计算机系统>第7章   链接 本章主要内容: 链接——静态链接.动态链接(链接又包括两个主要任务:符号解析和重定位) 符号——全局符号和本地符号.符号表.符号解析 链接文件的创建及引用——gcc.ar rcs.sharedj及fPIC命令参数 重定位——重定位条目.重定位符号引用(PC相对引用和绝对引用) 目标文件——可重定位目标文件(其中又详细介绍了ELF可重定位文件的结构及格式).可执行目标文件.共享目标文件 链接(linking)是将各种代码和数据部分收集起来并组合成为一个单一