Great Reality
Ints are not Integers, Floats are not Reals
对于 (x + y) + z = x + (y + z),无符号整形和有符号整形是成立的。
但是对于浮点数, (1e20 + -1e20) + 3.14 -> 3.14,而 1.e20 + (-1e20 + 3.14) = 0
typedef struct {
int a[2];
double d;
}struct_t;
double fun(int i) {
volatile struct_t s;
s.d = 3.14;
s.a[i] = 1073741824; /* Possibly out of bounds */
return s.d;
}
我们发现,当 i = 5 时,程序会崩溃。
Explanation:
C 语言不会在运行中做任何边界检查
对于下图,垂直链的每个块代表 4 字节。a 数组的两个元素都是 4 字节,d 是 8 个字节
当 i 大于 1 时,赋值 a[2] 会赋值给 d3 … d0,赋值 a[3] 会赋值给 d4 … d7,所以 i = 4 的时候正常,后面报不报错看栈的空间等。
Memory System Performance Example
下面有两端函数。
void copyij(int src[2048][2048], int dst[2048][2048])
{
int i, j;
for(int i = 0; i < 2048; i ++)
for(int j = 0; j < 2048; j ++)
dst[i][j] = src[i][j];
}
void copyji(int src[2048][2048], int dst[2048][2048])
{
int i, j;
for(int j = 0; j < 2048; j ++)
for(int i = 0; i < 2048; i ++)
dst[i][j] = src[i][j];
}
可以看出它们的区别在于一个行优先,一个列优先。而第一段代码运行速度远远快于第二段代码。以行优先代码的运行速度快于一列一列运行。
Everything is bits
整数、浮点数等都可以表示为二进制。
Data Representations
C Data Type | Typical 32-bit | Typical 64-bit | x86-64 |
---|---|---|---|
char | 1 | 1 | 1 |
short | 2 | 2 | 2 |
int | 4 | 4 | 4 |
long | 4 | 8 | 8 |
float | 4 | 4 | 4 |
double | 8 | 8 | 8 |
long double | - | - | 10/16 |
pointer | 4 | 8 | 8 |
Boolean Algebra
And: A & B = 1 when both A = 1 and B = 1
Or: A | B = 1 when either A = 1 or B = 1
Not: ~A = 1 when A = 0
Exclusive-Or(Xor): A^B = 1 when either A = 1 or B = 1, but not both 相同为 0,相异为 1
Representing & Manipulating
Representation
Width w bit vector represents subsets of {0, …, w - 1}
a
j
=
1
i
f
j
∈
A
a_j = 1~ ~if~ ~j~ ~\in A
aj=1 if j ∈A
01101001 代表 {0, 3, 5, 6}
76543210
01010101 代表 {0, 2, 4, 6}
76543210
Operations
符号 | 操作 | 二进制序列 | 集合 |
---|---|---|---|
& | Intersection 交 | 01000001 | {0, 6} |
| | Union 并 | 01111101 | {0, 2 ,3, 4, 5, 6} |
^ | Symmetric difference 对称差异 | 00111100 | {2, 3, 4, 5} |
~ | Complement 补 | 10101010 | {1, 3, 5, 7} |
Bit-Level Operations in C
位运算
Examples(Char data type)
∼
0
x
41
−
>
0
x
B
E
\sim0x41 -> 0xBE
∼0x41−>0xBE
∼
0100000
1
2
−
>
1011111
0
2
\sim01000001_2->10111110_2
∼010000012−>101111102
∼
0
x
00
−
>
0
x
F
F
\sim0x00 -> 0xFF
∼0x00−>0xFF
∼
0000000
0
2
−
>
1111111
1
2
\sim0000 0000_2->1111 1111_2
∼000000002−>111111112
∼
0
x
69
&
0
x
55
−
>
0
x
41
\sim0x69~ ~\&~ ~0x55 -> 0x41
∼0x69 & 0x55−>0x41
$\sim0110 1001_2 $
&
\&
&
0101010
1
2
−
>
0100000
1
2
0101 0101_2->0100 0001_2
010101012−>010000012
∼
0
x
69
∣
0
x
55
−
>
0
x
7
D
\sim0x69 ~~| ~~0x55 -> 0x7D
∼0x69 ∣ 0x55−>0x7D
∼
0110100
1
2
\sim 01101001_2
∼011010012
∣
|
∣
0101010
1
2
−
>
0111110
1
2
01010101_2 -> 01111101_2
010101012−>011111012
Logic Operations in C
&
&
,
∣
∣
,
!
\&\&, ||, !
&&,∣∣,!
view 0 as “False”
Anythings nonzero as “True”
Always return 0 or 1
Early termination
C 语言的逻辑运算只有 2 种结果, 0x00 和 0x01
取反是按结果取反,不是按位取反!
Examples(Char data type)
!
0
x
41
−
>
0
x
00
!0x41 -> 0x00
!0x41−>0x00 相当于对 true 取反,结果为 false, 为 0x00
!
0
x
00
−
>
0
x
01
!0x00 -> 0x01
!0x00−>0x01
!
!
0
x
41
−
>
0
x
01
!!0x41-> 0x01
!!0x41−>0x01
0
x
69
&
&
0
x
55
−
>
0
x
01
0x69~ ~\&\&~ ~0x55 -> 0x01
0x69 && 0x55−>0x01
0
x
69
∣
∣
0
x
55
−
>
0
x
01
0x69~ ~||~ ~0x55 -> 0x01
0x69 ∣∣ 0x55−>0x01
p
p~~
p
&
&
\&\&
&&
∗
p
*p
∗p (avoids null pointer access) 在访问之前先判断是否为空指针,如果是空指针,会提前终止。
Shift Operation
Left Shift: x << y
Shift bit-vector x left y positions, Throw away extra bits on left
Fill with 0’s on right
Right Shift: x >> y
Shift bit-vector x right y position, Throw away extra bits on right
Logical Shift
Fill with 0’s on left
Arithmetic Shift
Replicate(复制) most significant bit on left
对于右移运算,分为算数右移和逻辑右移。他们的填充规则不一样。算数右移填充的是符号位的数字。
Argument x | 0110 0010 |
---|---|
<< 3 | 0001 0000 |
Log. >> 2 | 0001 1000 |
Arith. >> 2 | 0001 1000 |
Argument x | 1010 0010 |
---|---|
<< 3 | 0001 0000 |
Log. >> 2 | 0010 1000 |
Arith. >> 2 | 1110 1000 |
Undefined Behavior
Shift amount < 0 or >= word size
Numeric Ranges
Unsigned Values
U
M
i
n
=
0
−
>
000...0
UMin = 0 -> 000...0
UMin=0−>000...0
U
M
a
x
=
2
w
−
1
−
>
111...1
UMax = 2^w - 1 -> 111...1
UMax=2w−1−>111...1
U
M
a
x
=
2
×
T
M
a
x
+
1
UMax = 2 \times TMax + 1
UMax=2×TMax+1
Two’s Complement Values (补码)
T
M
i
n
=
−
2
w
−
1
−
>
100...0
TMin = -2^{w - 1} -> 100...0
TMin=−2w−1−>100...0
T
M
a
x
=
w
w
−
1
−
1
−
>
011...1
TMax = w^{w - 1} - 1 -> 011...1
TMax=ww−1−1−>011...1
∣
T
M
i
n
∣
=
T
M
a
x
+
1
|TMin| = TMax + 1
∣TMin∣=TMax+1
Other Values
M i n u s 1 − > 111...1 Minus~1 -> 111...1 Minus 1−>111...1
Values for W = 16
Decimal | Hex | Binary | ||
---|---|---|---|---|
UMax | 65535 | FF FF | 11111111 11111111 | |
TMax | 32767 | 7F FF | 01111111 11111111 | |
TMin | -32768 | 80 00 | 10000000 00000000 | |
-1 | -1 | FF FF | 11111111 11111111 | |
0 | 0 | 00 00 | 00000000 00000000 | |
U M a x = 2 × T M a x + 1 UMax = 2 \times TMax + 1 UMax=2×TMax+1 |
From Binary to Unsigned
B 2 U ( X ) = ∑ i = 0 w − 1 x i 2 i B2U(X) = \sum_{i = 0}^{w - 1}x_i 2^i B2U(X)=∑i=0w−1xi2i
From Binary to Two’s complement
B
2
T
(
X
)
=
−
x
w
−
1
2
w
−
1
+
∑
i
=
0
w
−
2
x
i
2
i
B2T(X) = -x_{w - 1} 2^{w - 1} + \sum_{i = 0}^{w - 2} x_i 2^i
B2T(X)=−xw−12w−1+∑i=0w−2xi2i
对于补码,最高位称为符号位,为 1 代表负数。
Unsigned & Signed Numeric Values
X | B2U(X) | B2T(X) |
---|---|---|
0000 | 0 | 0 |
0001 | 1 | 1 |
0010 | 2 | 2 |
0011 | 3 | 3 |
0100 | 4 | 4 |
0101 | 5 | 5 |
0110 | 6 | 6 |
0111 | 7 | 7 |
1000 | 8 | -8 |
1001 | 9 | -7 |
1010 | 10 | -6 |
1011 | 11 | -5 |
1100 | 12 | -4 |
1101 | 13 | -3 |
1110 | 14 | -2 |
1111 | 15 | -1 |
观察可知,负数与对应的正数关系是,正数取反加 1 |
Casting
如果有符号数和无符号数混合,有符号数会被隐式转换为无符号数。
−
1
>
0
U
-1 > 0U
−1>0U 因为
−
1
-1
−1 会被转换为无符号数
1111
1111
1111~ ~1111
1111 1111 也就变成
U
M
a
x
UMax
UMax
W
=
32
,
T
M
I
N
=
−
2147483647
,
T
M
A
X
=
2147483647
W =32,~TMIN = -2147483647, ~TMAX=2147483647
W=32, TMIN=−2147483647, TMAX=2147483647
2147483647
U
<
−
2147483647
−
1
2147483647U < -2147483647 - 1
2147483647U<−2147483647−1
(
u
n
s
i
g
n
e
d
)
−
1
>
−
2
(unsigned)-1 > -2
(unsigned)−1>−2
2147483647
<
2147483648
U
2147483647 < 2147483648U
2147483647<2147483648U
2147483647
>
(
i
n
t
)
2147483648
U
2147483647 > (int)2147483648U
2147483647>(int)2147483648U, 对于 32 位系统来说,
i
n
t
int
int 范围是
−
2147483648
∼
2147483647
-2147483648 \sim 2147483647
−2147483648∼2147483647,
u
n
s
i
g
n
e
d
i
n
t
unsigned~int
unsigned int 范围是
0
∼
4294967295
0 \sim 4294967295
0∼4294967295,所以将
2147483648
U
2147483648U
2147483648U 转换为
i
n
t
int
int, 会溢出。
考虑以下的代码:
对于 x = TMin, 他的输出还是 TMin。
因为
−
x
=
∼
x
+
1
-x = \sim x + 1
−x=∼x+1
对于 TMin 1000…0 取反 0111…1 再加 1, 1000…0 所以结果还是 TMin
if(x < 0)
return -x;
else
return x;
以下代码会造成无限循环。 i i i 会从 0 0 0 变为 U M a x UMax UMax (个人认为是 0 减 1 为 -1, 表示为全 1,则解释为 UMax)
unsigned int i;
for(i = n - 1; i >= 0; i --) {
f(a[i]);
}
对于下面的代码, s i z e o f ( c h a r ) sizeof(char) sizeof(char) 实际上返回的是 无符号数。 i i i 为有符号数,有符号数和无符号数比较会被转换为无符号数。 i − s i z e o f ( c h a r ) > = 0 i - sizeof(char) >= 0 i−sizeof(char)>=0 这个条件就会一直成立,无限循环。
int i;
for(i = n - 1; i - sizeof(char) >= 0; i --) {
f(a[i]);
}
Sign Extension
Task:
Given w-bit signed integer x
Convert it to (w+k)-bit integer with same value
Rule:
Make k copies of sign bit
X
′
=
x
w
−
1
,
.
.
.
,
x
w
−
1
,
x
w
−
2
,
.
.
.
,
x
0
X' = x_{w-1}, ..., x_{w-1}, x_{w - 2}, ..., x_0
X′=xw−1,...,xw−1,xw−2,...,x0
对于
1110
1110
1110,符号位权重为
−
2
3
=
−
8
-2^3=-8
−23=−8,将它左移一位,变为
11110
11110
11110,原先符号位位置上的权重变为了
8
8
8,而新的符号位权重为
−
2
4
=
−
16
-2^4=-16
−24=−16,
−
16
+
8
=
−
8
-16 + 8 = -8
−16+8=−8, 所以并不改变总和的效应。
Converting from smaller to larger integer data type, C automatically performs sign extension.
short int x = 15213;
int ix = (int) x;
short int y = -15213;
int iy = (int) y;
Two’s Complement Addition
-5 + 3
1011
+0011
1110 -> -2
-3 + 5
1101
+0101
10010 -> 0010 -> 2
-3 + -5
1101
+1011
10000 -> 0 负溢出
位运算
移位指令比乘法指令花的时间更少。
u
<
<
k
u << k
u<<k 相当于
u
×
2
k
u \times 2^k
u×2k
u
>
>
k
u >> k
u>>k 相当于
⌊
u
/
2
k
⌋
\lfloor {u / 2^k} \rfloor
⌊u/2k⌋
前面介绍过算数右移与逻辑右移。算数右移可以保持符号位。例如 1010 为 -6,算数右移 1 位,则为 1101 为 -3。但是此时如果再右移 1 位,得到 1110 为 -2。结果出现问题。
特别的,对于负数的除法,如果需要右移
k
k
k 位,我们需要先加上偏移量
2
k
−
1
2^k - 1
2k−1。
1101 + 1 = 1110 此时再右移 1 位,得到 1111 为 -1。
正数变负数
x
−
>
−
x
x -> -x
x−>−x 需要对所有位取反,再 + 1。
0101 -> 5
1010 + 1 = 1011 -> -5
使用 Unsigned 一些方式
unsigned i;
for(i = cnt - 2; i < cnt; i --)
a[i] += a[i + 1];
Better Version:
size_t i;
for(i = cnt - 2; i < cnt; i --)
a[i] += a[i + 1];
/*
1. Data type size_t defined as unsigned value with length = word size
2. Code will work even if cnt = UMax
*/
然而,当 cnt 为 signed 并且小于 0 时。会有问题。cnt 会被转换为无符号数,就会变得非常大,导致无限循环。
大端序和小端序
Big Endian
高位字节存入低地址,低位字节存入高地址。
上图从左往右 01234567
Little Endian
低位字节存入低地址,高位字节存入高地址。
从右往左 01234567
字符串
字符串以 null 结尾,也就是 ‘0’
char S[6] = '18213';
字符 ‘0’ 为 0x30,数字字符 i 为 0x30 + i
对于字符串数组来说,大端法和小端法存储没有区别。因为字符是一个字节一个字节存储,而每个都是一个整体。
[!NOTE]
例如对于 0x12345678 来说,这是一个整体,0x12 是整体的高位,0x78 是整体的低位,存储就是 0x78 0x56 0x34 0x12。而对于字符数组,内部组织形式是一个字节一个字节,数组相当于是 0x12 0x34 0x56 0x78,每个字符是一个整体。
易错
1.如果 x < 0, 那么 ( ( x ∗ 2 ) < 0 ) ((x * 2) < 0) ((x∗2)<0) 吗? 错误,因为可能会负溢出, x ∗ 2 x * 2 x∗2 可能会是一个正数。
2.如果一个数 x,x & 7 == 7,也就是最低的 3 位为 111,如果 (x << 30) 那么结果 < 0。
3.Is ux > -1? 错的。无符号数和有符号数作比较,有符号数被隐式转换为无符号数。-1 就会被转换为 UMax。
4.如果 x > y,那么 -x < -y 一定成立吗? 错误的,因为如果 y 为 TMin,我们直到 ∣ T M i n ∣ = T M a x + 1 |TMin| = TMax + 1 ∣TMin∣=TMax+1。对 TMin 取相反数,也就是 TMin 的位取反再 + 1,那就变成 TMax + 1,发生正溢出。又变回了 TMin。
5.x >= 0 那么 -x <= 0 吗? 正确的
6.x <= 0 那么 -x >= 0 吗? 错误的,比如 TMin。