-
凡所有相,皆是虚妄。若见诸相非相,则见如来。本文所述皆指常例,凡所常例即有异处。学者当知:细节中有魔鬼,这魔鬼就是无知。C语言就是相,如来就是汇编是机器码。无论是什么语言,最后都要落实到汇编来编译,到机器码去执行。机器码难于读,汇编难于写,C对汇编语言进行了抽象。实例:
-
最常见的符号 C++ ,用伪代码来表示 C=C+1 。为何要有++出现?因为C是中级语言,汇编中INC指令可把代码精简。
-
在实际 C++ 也不一定就是加一,如下表
-
int c ; c++ 是 inc 把变量c的值加一,细节:可能会溢出变成负数
-
char c ; c++ 是 inc 把变量c的值加一,细节:可能会溢出变成负值
-
char* c ; c++ 是 inc 把变量c的值加一,细节:指针操作可能会在*c时得到的是脏数据。
-
int * c ; c++ 这里就有点复杂了,变成下面的汇编
-
MOV EAX , C ; 将变量C的值取出来,不是把c指向的单元内容取出来
-
ADD EAX , sizeof(int) 这个数值依赖于机器字长编译器
-
在 16位地址的处理器,比如8051单片机上 = 16/8 =2
-
在 32位地址的处理器上,比如80486 = 32/8 =4
-
在 64位地址的处理器上,比如IA64 = 64/8 =8
-
MOVE C , EAX ; 计算后的结果存到C变量的实际内存中去。在逻辑上就是跳过了int型大小的地址空间。
-
这里还有魔鬼么?是的,在实际中这里还会有很多的魔鬼,例:
-
魔鬼1:硬件中断,如果中断处理程序不好,那么在计算完成后,再存的时候可能值已经被修改。
-
魔鬼2:虚拟内存的对换,结果就是存的时间会很长,需要页换进换出才能执行完。
-
当然了这些魔鬼在使用时上极少会遇到,比如内存对换有操作系统来完成了。
-
-
开宗明义:程序 = 数据 + 算法 学习编程语言,说到底,就是学习数据和算法,用算法来操作数据,即是程序。
-
-
C语言之数据
-
数据与数据类型的区别:类型是名,数据是实。名实相符,就是正确的数据,名实不符就是脏数据。CPU不管数据是正确的还是脏的只管根据指令去执行。
-
-
数据类型就是相,数值是如来是本质。计算机世界中只有数据,没有类型。所有的类型都是从人类可理解的角度才产生的。这个字节和那个字节对于计算机来说,没有任何本质上的区别。以下均以32位机为例:
-
char型就是一个字节
-
short型就是两个字节
-
int型就是四个字节
-
float型就是四个字节
-
double型就是八个字节
-
各类指针都是四个字节
-
内存数据 01 02 03 04 05 06 07
-
char 读出来的是 0x01
-
short 读出来是 0x0201 <-- little endian
-
0x0102 <-- big endian
-
int 读出来 0x04030201 <-- little endian
-
普通变量,名实相符,用其名就是取其实。 而&操作则是取地址,对应汇编LEA指令
-
指针变量,只有名无实。需要用*操作即 (*指针变量) 取其实。指针变量是用来来挑选数据的。
-
-
C语言之算法
-
算法基础是指令。顺序执行就是简单指令的堆砌。跳转则打乱原有的顺序,产生了变化。
-
在汇编中有多种跳转
-
无条件跳转 JMP
-
为零跳转 JZ
-
非零跳转 JNZ
-
等等
-
跳转是基于标志的CPU有很多标志位比如:JZ JNZ指令就是利用Z标志位决定如何跳转CMP EAX,0执行后,如果EAX = 0 那么Z标志位为真。用 JZ就跳转。在C语言中不需要考虑太多这么细的,只要按人类逻辑就可以了。if( a == 0 ){} if( 0 ){}
-
那么接下来的所有程序控制都是跳转的变形。switch , if ,while, for 都是对汇编进行了可读化的整合。
-
WHILE循环
-
while( a == 0 ){code_a}
-
L_WHILE: ;标号
-
CMP A , 0 ;比较A的值
-
JNZ L_WHILE_END ;不是0就跳
-
code_a ;执行代码块
-
JMP L_WHILE ;跳转到前面去再次执行
-
L_WHILE_END
-
-
do{code_a}while(a==0);
-
L_WHILE:
-
code_a ;先执行代码 code_a
-
CMP A , 0 ;再比较
-
JZ L_WHILE ;是0就跳回前面
-
L_WHILE_END: ;不是0就继续向下执行
-
-
条件执行
-
if( a == 0 ){code_a}else{code_b}
-
L_IF_BEGIN:
-
CMP A, 0 ;比较
-
JZ L_IF ;是0跳到L_IF做code_a
-
code_b; ;非0执行code_b
-
L_ENDIF:
-
后继指令
-
L_IF:
-
code_a;
-
JMP L_END_IF ;回到L_ENDIF往下执行后继指令
-
-
FOR循环
-
for( code_a ; a==0 ; code_b){
-
code_c;
-
}
-
L_FOR_BEGIN:
-
code_a ;先做code_a 通常是循环变量的初始化
-
L_FOR_TEST: ;先做条件判断
-
CMP A,0
-
JNZ L_FOR_END ;不成立就退出
-
code_c;
-
code_b;
-
JMP L_FOR_TEST ;跳转再判断
-
L_FOR_END
-
-
SWITCH结构
-
switch(A){
-
case 1:
-
code_1;
-
case 2:
-
code_2;
-
break;
-
default:
-
code_default;
-
break;
-
}
-
静态数据
-
MAP: ;构造一个静态跳转表
-
.L_CASE1
-
.L_CASE2
-
;执行期
-
L_SWITCH_BEGIN:
-
MOVE EAX , A ;取A的值
-
CMP EAX,2
-
JG L_SWITCH_DEFAULT ;大于2做default
-
CMP EAX,0
-
JL L_SWITCH_DEFAULT ;小于0也做default
-
DEC EAX ;EAX--
-
LEA EBX,MAP ;取得跳转表的地址
-
SHIFT EAX,2; ;左移2位,也就是乘以4,因为一个地址指针占4个字节
-
ADD EBX,EAX ;合成最终的地址
-
JMP EBX ;跳转到L_CASE1 也可能是 L_CASE2
-
L_CASE1:
-
code_1 ;没有break,所以做完继续向下
-
L_CASE2:
-
code_2
-
JMP L_SWITCH_END ;break转换成JMP
-
L_SWITCH_DEFAULT:
-
code_default
-
JMP L_SWITCH_END ;break转换成JMP,汇编优化时,这条会被去掉
-
L_SWITCH_END: ;switch结构结束
-
说明:case 1 中没有break,所以做完code_1 会接着做code_2 ,这只是通常。如果有特别的优化,也可能做完code1 以后就跑飞了,到不确定的哪个地方了,具体的地方,要看编译器如何生成汇编程序。在常见的环境下都是接着做code_2。
-
-
C语言之指针:
-
指针的本质就是变量中存储的是其他数据的地址。
-
地址:一号楼205室,这只是"名","实"可以是会议室也可以是办公室也可能根本不存在。
-
数据指针,指向的是数据,如何理解数据,由其类型决定。
-
函数指针,指向的是代码。可通过 变量名() 去执行相应的代码。
-
形式:type (*f)(参数列表);
-
其中的type表示运行后会有什么样的返回值。
-
参数列表,则明确了调用时需要什么参数。
-
实质:四字节的内存空间
-
DWORD f ;
-
运行时:
-
多条PUSH或MOV携带好传入的参数
-
MOV EAX , f ; 获得运行时的指针的值
-
CALL EAX ; 调用子程序
-
数组指针:指向一个数组的首成员的地址。
-
int * a , b[10];
-
a=b ; 等效于 a= &b[0];
-
指针数组:一个数组,里面放的数据是指针。可以理解成一本电话本。里面的每一个数据都是其他实体的地址。
-
int * a[3] , b[10];
-
a[0] = &b[0] ; 等效 a[0] = b ;
-
-
C语言之数组
-
形式: 类型 名称 [ 大小 ]
-
实质: 产生了 sizeof(类型) * 大小 的内存块
-
特例: 大小为0,产生一个地址标号,即为某个地址取了一个名字。
-
int a[0] , b[20];
-
因为C语言不对越界进行检查,所以在普通的编译器下,用 a[15] 和 b[15] 是一样的。
-
在运行时,没有为a分配内存空间,因为a和b连续分配,所以a和b是一样的。
-
-
C语言之结构、联合和枚举
-
结构struct:串行分配空间
-
联合union :并行共用空间
-
枚举enum : 对整型限制值域
-
struct S{ union U{ enum E{
-
int a ; int a; a,b=2
-
char b ; char b; }ea,*eb;
-
}sa,*sb; }ua,*ub;
-
定义结构S及实例 sa; 定义联合U及实例ua; 定义枚举E和实例ea
-
sizeof(sa) 至少是 sizeof(int)+sizeof(char) 字节,实际在编译时有字节对齐的要求,暂不表。
-
sizoof(ua) 至少是 sizeof(int)>sizeof(char)?sizeof(int):sizeof(char),实际分配的空间也跟字节对齐有关。
-
sizeof(ea) 由编译器的字长决定,通常可以理解为 int 但其取值只能是 a 和 b 也就是0 和 2
-
在结构中a,b是独立的成员互不影响。
-
在联合中b,则是a的第一个字节的内容
-
+-----+-----+-----+-----+-----+
-
|byte1|byte2|byte3|byte4|byte5|
-
+-----+-----+-----+-----+-----+
-
|----------a------------|--b--| 联合
-
|--b--+-------a---------| 枚举
-
如上图示,ua.b 就是存取的图中的byte1 ,改变了ua.b的值,那么读取ua.a也变化了。
-
sa.b 就是图中的byte5;
-
成员的引用固定变量中的成员用.来取得,指针指向的成员用 ->
-
sa.a sb->a ua.a ub->b
-
-
-
附一:浮点型数据的解读
-
float和double的范围是由指数的位数来决定的。
-
float的指数位有8位,而double的指数位有11位,分布如下:
-
float:
-
1bit(符号位) 8bits(指数位) 23bits(尾数位)
-
double:
-
1bit(符号位) 11bits(指数位) 52bits(尾数位)
-
于是,float的指数范围为-127~+128,而double的指数范围为-1023~+1024,并且指数位是按补码的形式来划分的。
-
其中负指数决定了浮点数所能表达的绝对值最小的非零数;而正指数决定了浮点数所能表达的绝对值最大的数,也即决定了浮点数的取值范围。
-
float的范围为-2^128 ~ +2^128,也即-3.40E+38 ~ +3.40E+38;double的范围为-2^1024 ~ +2^1024,也即-1.79E+308 ~ +1.79E+308。
-
-
float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。
-
float:2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字;
-
double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。
阅读(1503) | 评论(0) | 转发(0) |