笔记内容来自:程序的表示、转换与链接
作为理解计算机基础概念的指导路径。理解如下核心概念。
用“系统思维"分析问题
例子1
- ISO C90标准下,在32位系统上以下C表达式的结果是什么?
-2147483648 < 2147483647
false(与事实不符)! Why?
- 以下关系表达式结果呢?
int i = -2147483648;
i < = 2147483647
true! Why?
- -2147483648 - 1 < 2147483647,结果怎样?
如何理解该问题呢?
- 编译器如何处理字面量
- 高级语言中运算规则
- 高级语言与指令之间的对应
- 机器指令的执行过程
- 机器级数据的表示和运算
例子2
sum (int a[], unsigned len)
{
int i, sum = 0;
for(i = 0; i<= len-1; i++>)
sum += a[i];
return sum;
}
当参数len为0时,返回值应该是0,单身在机器上执行时,却发生访问异常。但当len为int型时则正常。Why?
如何才能理解?
- 高级语言中运算规则
- 机器指令的含义和执行
- 计算机内部的运算电路
- 异常的检查和处理
- 虚拟地址空间
例子3
若x和y为int型,当x=65535时,y=x*x;y的值为多少呢?
#include <stdio.h>
static int multi(int a, int b)
{
return a*b;
}
int main(int argc, const char *argv[])
{
int a = 65535;
int b = 65535;
int c;
c = multi(a, b);
printf(" %d * %d = %d\n", a, b, c);
return c;
}
结果是65535 * 65535 = -131071
现实世界中,x的平方是大于等于0的,但是在计算机接收中并不一定成立。
对于任何int型变量x和y,(x>y) == (-x < -y>)总成立吗?
当x=-2147483648,y任意(除-2147483648)时不成立,Why?
现实世界中成立,但是在计算机世界中是不一定成立的。
如何才能理解
- 机器级数据的表示
- 机器指令的执行
- 计算机内部的运算电路
例子4
main.c
int d=100;
int x=200;
int main()
{
p1();
printf("d=%d, x=%d\n", d, x);
return 0;
}
p1.c
double d;
void p1()
{
d = 1.0;
}
打印的结果是什么?
d=0, x=1 072 693 248
Why?
如何才能理解
- 机器级数据的表示
- 变量的存储空间分配
- 数据的大端/小端存储方式
- 连接器的符合解析规则
例子5
/*复制数组到堆中,count为数组元素个数*/
int copy_array(int *array, int count){
int i;
/* 在堆中申请一块内存*/
int *myarray = (int*)malloc(count*sizeof(int));
if(myarray == NULL)
return -1;
for(i = 0; i < count; i++)
myarray[i] = array[i];
return count;
}
当count等于2的30次方加一的时候,程序会发生什么情况?
当参数count很大时,则countsizeof(int)会溢出。如count等于2的30次方加一时,countsizeof(int)=4。则堆(heap)中大量数据被破坏。
如何才能理解
- 乘法运算及溢出
- 虚拟地址空间
- 存储空间映射
例子6
int a = 0x80000000;
int b = a / -1;
printf("%d\n", b);
运行结果为-2147483648
objdump反汇编代码,得知除以-1被优化成去负指令neg,故未发生除法溢出。
int a = 0x80000000;
int b = -1;
int c= a/b;
printf("%d\n", c);
运行结果为Flfoating point exception,显然CPU检测到了溢出异常。
为什么两者结果不同
**a/b用除法指令IDIV实现,但它不生成OF标志,那么如何判断溢出异常的呢?**实际上是,“除法错”,异常#DE(类型0)
Linux中,对#DE类型发SIGFPE信号。
如何理解
- 编译器如何优化
- 机器级数据的表示
- 机器指令的含义和执行
- 计算机内部的运算电路
- 除法异常的处理
例子7
main()
{
double a = 10;
printf("a = %d\n", a);
}
在IA-32上运行时,打印结果为a=0
在X86-64上运行时,打印出来的a是一个不确定值
为什么?
如何才能理解
- IEEE 754的表示
- X87 FPU的体系结构
- IA-32和x86-64中过程调用的参数传递
- 计算机内部的运算电路
例子8
double fun(int i)
{
volatile doubel d[1] = {3.14};
volatile long int a[2];
a[i] = 1073741824;/*Possibly out of bounds*/
return d[0];
}
对上述C语言函数,i=0~4时,fun(i)分分别返回什么值
fun(0) —> 3.14
fun(1) —> 3.14
fun(2) —> 3.1399998664856
fun(3) —> 2.00000061035156
fun(4) —> 3.14,然后存储保护错
为什么?
如何才能理解
- 机器级数据的表示
- 过程调用机制
- 栈帧中数据的布局
例子9
void copyij(int src[2048][2048], int dst[2048][2048])
{
int i,j;
for(i = 0; i< 2048; i++)
for(j = 0; j< 2048; j++)
dst[i][j]= src[i][j];
}
void copyij(int src[2048][2048], int dst[2048][2048])
{
int i,j;
for(j = 0; j< 2048; j++)
for(i = 0; i< 2048; i++)
dst[i][j]= src[i][j];
}
以上两个程序功能完全一样,算法完全一样,因此,时间和空间复杂度完全一样,执行时间一样吗?
实际上按行优先是按列优先的21倍快。
如何才能理解
- 数组的存放方式
- Cache机制
- 访问局部性
例子10
int main(int argc, char* argv[])
{
int a=10;
double *p(double*)&a;
printf("%f\n",*p); //结果为0.0000000
println("%f\n",(double(a))); //结果为10.000000
return 0;
}
关键差别在于一条指令:fldl 和 fildl
如何理解
- 数据的表示
- 编译(程序的转换)
- 局部变量在栈中的位置
小结
- 关键是使用例子引入的核心概念。
- 这些概念作为理解的核心。