Part1 Number and C Basics
Number representation
1.几种进制
- Decimal
- Binary 0bxxxx (x是0或1)
- Hex 十六进制 0xxxxx(0-F)
2.进制的转换
3.数字表示方法
最重要的还是补码(2’s complement)
其他还有 1’s complement, sign and magnitude
bias notation 移码
以一个数为base,用unsigned int为bias,表示两数之和
用于表示浮点数中的指数部分
例如 bias = -127
0b00000000 = -127
0b11111111 = 128
参考链接
4.overflow
机器中表示一个数的位有限,超过范围就会溢出
补码形式加法可能溢出的情况:正+正,负+负
5.sign extension
用更多的位数表示相同的数字
具体方式(补码形式): 用符号位填满多出来的位,符号位与原符号位相同,剩下较低的其余位不变
floating point 浮点数
用二进制的科学计数法来表示小数,小数点位置不固定,所以称为浮点数
既有范围的广度,又有位数的精度
科学计数法
IEEE 754 Standard
所有计算机通用的浮点数储存形式,保持高精度,支持实数数学运算,可以处理数学错误(除0等等),与二的补码兼容,可以让计算机无需理解浮点,即可进行大小比较
- 32bit表示(单精度)
- 1bit表示符号位(1表示负数,0表示正数)
- 8bit表示指数(bias=-127的bias notation)
- 23bit表示小数部分
指数
使用-127bias,则可以看做unsigned int,利于进行大小比较,一个数看上去越大(1相对更多),则实际大小越大
对于一个已知的指数,+127,再用unsigned int表示,可以得到储存的形式
指数范围是-127到128,其中128表示正无穷,-127表示0 如此处理过大的数,避免溢出
小数部分(significant)
23bits,实际表示形式是
1.significant
这样可以少储存一位
双精度 double
Special number representation 特殊数字
0的表示(Representing Zero)
由于隐性表示的1,32位全0实际上表示1.0*2-127
在此规定:小数部分和指数部分全0即表示0
符号位不同表示+0和-0(一个无限接近0的数字总有符号)
正负无穷 ± ∞ ± \infty ±∞
小数部分全0,指数部分全1,符号位表示正负
特性:在754中作为一个数表示,可以通过除0得到
Not a Number(NaN)
- 无意义的数字 比如 0/0 sqrt(-4)
- Op(NaN,number) = NaN
- 可用于debug
- 表示:指数全1 小数非0
Denorm Numbers
有意义的最小的正数为小数全0 指数为1(-126) 即 1.0 * 2-126.
第二小的是小数为 2-126 + 2-149 (1.000…01 * 2-126)
使用Denorm Number填补其和0的空隙
当指数全0 小数非0 此时指数实际为-127
但规定移去浮点左侧默认的1,则指数为-126,整数为0,小数部分由significant表示
最小的Denorm Number 2-149
最大的Denorm Number 2-126 - 2-149(0.111…11 * 2-126)
注意:!!此时虽然指数全0,理论上是-127,但是计算的时候要当做-126
Limitations
- 数字过大(Overflow)
- 数字过小(Underflow)
- 精度不够(Rounding)
- 无法表示范围内全部整数(Gaps and distribution of values)
- 算术运算未必满足结合律(Associativity)(Small + Big + Small != Small + Small + Big)
这里260 和 2-15 差了75位,但实际上significant只有23位,会发生precision loss
该系统中,significant为25位,2-10与222相加会发生精度丢失,其余时候不会
所以bd计算结果为精确的(2-1+2-10)
(也算是解答了个人平时编程时候遇到的问题)
Conversion
- FP tp Decimal
- Scientific Number to Fp
C Basics
- C语言需要编译器将其编译为architecture-specific机器语言(由01组成,不同的操作系统和CPU编译出来的内容不同)
- 可以对单独文件重新编译
- 运行速度较快(相比于java和python)
- 同样的文件在新的系统上必须重新编译(architecture-specific)
- 每次对项目进行edit,run之前都需重新编译
结构体
结构体内部数据的储存是对齐的
例如:32位操作系统中,4bytes为一个word,而char只需要一个byte的空间
Struct foo{
int a;
char b;
struct foo *c;
}
-
4 bytes for a
-
1 byte for b
-
3 unused bytes
-
4 bytes for c(指针需要一个word的空间)
-
sizeof(struct foo) == 12
为结构体分配空间时,会按照变量的顺序,按照对齐的原则,进行padding,保证对齐(4bytes放在地址为4的整数倍,2bytes在2的整数倍等等),因此变量顺序不同,结构体的size可能也不同
Union
与结构体类似,但是仅用最大元素的大小的空间,union内所有元素都使用相同的空间,本质上是用同一串bits,当做不同类型的变量
数组和指针
数组在本质上是指向头元素的指针
int foo(int array[],unsigned int size){
return sizeof(array);
}
这种情况下,在函数内部的环境中,编译器仅仅知道array是一个指针,并不知道这个数组的大小,所以最后返回sizeof(int*)
int main(void){
int a[10];
printf("%d/n",sizeof(a));
return 0;
}
在main函数环境内声明的数组a,这时编译器知道a是大小为10的数组,所以输出10sizeof(int)
本质上是要求这个array在当前函数的stack frame中
string
point arithmetic
对于指针p,p+1实际是p + sizeof§
有效的指针运算:
- 指针加整数
- 同数组的指针相减
- 指针的比较(>, <, ==, !=>)
- 将指针与NULL比较
increment and dereference
- 前缀运算符的顺序为从右往左
*++p,先让p自增,再返回已经自减的p所指向的元素
++*p,让p指向的元素自增 - 后缀运算符(p++)优先级高于前缀运算符,但是最后生效
*p++,因为++优先级高,所以与p绑定,但是先不生效,先返回p指向的值,之后再让p自增
(*p)++,同样返回p指向的值,但是++与*p绑定,在返回后,p指向的值自增
C’s memory layout
Stack
每调用一个新函数,栈向下开辟新空间作为function的frame,内部储存各种信息(location of caller function,function arguments,space for local variables)
函数调用结束,退栈,栈顶指针(SP)回到上一个frame,原先的当前函数内容没有被删除,而是成为garbage
在函数返回后,函数内部变量y已经变成garbage,所以不要返回函数内变量的指针
Static Data
主要是global variables和 string literal
string literal:char *str = “abc”;双引号字符串
Code
装载C程序
Heap – Dynamic Memory Allocation
malloc(n)
在heap上开辟一个n bytes的空间(garbage 未初始化),返回头部位置的指针
int *p = (int*) malloc(n*sizeof(int));//一个长度为n的int数组
free§
释放heap上p指针为头部的block空间
calloc
开辟空间,指定元素个数和大小
Realloc§
重新分配p指针的空间,改变空间大小
返回的是新空间头部的指针,未必是原来的p
Endian
大端和小端,对于含有多个bytes的数据在内存的存储方式
C Memory Errors
- 使用未初始化的变量
- 使用并未拥有的Memory(访问空指针的成员,return函数内定义的栈上的变量,为string分配内存时未考虑terminator)
- 使用未分配的内存(下标越界,strcpy时src长度大于dest分配的长度)
- 释放不合法的内存(释放内存未释放block head内存,比如free(p+1),或者二次释放内存)
- 内存泄漏(申请的内存未释放)