存档在 ‘系统原理’ 分类

MySQL Workbench建表时 PK NN UQ BIN UN ZF AI 的含义

2015年7月16日

PK
Belongs to primary key
作为主键

NN
Not Null
非空

UQ
Unique index
不能重复

BIN
Is binary column
存放二进制数据的列

UN
Unsigned data type
无符号数据类型(需要整数形数据)

ZF
Fill up values for that column with 0’s if it is numeric
填充0位(例如指定3位小数,整数18就会变成18.000)

AI
Auto Incremental
自增长

用户级线程和内核级线程

2013年9月25日

1 .内核级线程:切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态;可以很好的利用smp,即利用多核cpu。windows线程就是这样的。

2. 用户级线程内核的切换由用户态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu,目前Linux pthread大体是这么做的。

线程的实现可以分为两类:用户级线程(User-Level Thread)和内核线线程(Kernel-Level Thread),后者又称为内核支持的线程或轻量级进程。在多线程操作系统中,各个系统的实现方式并不相同,在有的系统中实现了用户级线程,有的系统中实现了内核级线程。
» 阅读更多: 用户级线程和内核级线程

类型转换一些细节

2013年8月28日

已知:signed char a = 0xe0,unsigned char b = a, unsigned int c = a;
那么a在内存中的二进制形式为:1110 0000,同理b在内存中也是为1110 0000,同理c的一个字节也为1110 0000;

对于a来说,得知它为一个负数。所以a的值为224 – 256 = -32;
对于b来说,得知它为一个正数,所以b的值为224
对于c来说,因为a的值为-32,所以c的值应该为224 – 2^32 = 0xffffffe0(hex).

补码认识

2013年8月28日

关于补码,看过一些书籍和网文,基本都是在“求反加一”的方法、步骤上反复强调,而对于补码的本质和定义,讨论的不足。这就对初学者的造成了误导,使得很多人都纠结在-128的补码求取过程中。
关于反码和原码,大家都是在郑重其事的讲解,其实,学过的人都知道,它们的重要性是 0 !
做而论道把自己对于补码的认识写在下面,但愿对读者有些帮助。

加法器
计算机里面,只有加法器,没有减法器,所有的减法运算,都必须用加法进行。
即:减去某个数字(或者说加上某个负数)的运算,都应该研究如何用加法来完成。

模、补数
在日常生活当中,可以看到很多这样的事情:
把某物体左转 90 度,和右转 270 度,在不考虑圈数的条件下,最终的效果是相同的;
把分针倒拨 20 分钟,和正拨 40 分钟,在不考虑时针的条件下,效果也是相同的;
把数字 87,减去 25,和加上 75,在不考虑百位数的条件下,效果也是相同的;
……。
上述几组数字,有这样的关系:
  90 + 270 = 360
  20 + 40 = 60
  25 + 75 = 100
式中的 360、60 和 100,就是“模”。
式中的 90 和 270、20 和 40,以及 25 和 75,就是一对对“互补”的数字。

知道了“模”,求某个数字的“补数”,就是轻而易举的了:
如果模为 365,数字 120 的补数为:365 – 120 = 245。

用补数代替原数,可把减法转变为加法。出现的进位就是模,此时的进位,就应该忽略不计。

二进制数的模
前面说过的十进制数 25 和 75,它们是 2 位数的运算,模是 100,即 1 的后面加上 2 个 0。
如果有 3 位数参加运算,模就是 1000,即 1 的后面加上 3 个 0。
这里的 1000,是十进制数的一千,可以写成 10^3,即 10 的 3 次方。
推论:有多少位数参加运算,模就是在 1 的后面加上多少个 0。

对于二进制数字,模也是这样推算。
如果是 3 位二进制数参加运算,模就是 1000,即 1 的后面加上 3 个 0;
那么当 8 位二进制数参加运算,模就是 1 0000 0000,即 1 的后面加上 8 个 0。
16 位二进制数参加运算,模可就大了,是 1 的后面加上 16 个 0。
注意:这里提到的 1、0,都是二进制数。
8 位二进制数的模可以按照十进制写成 2^8,即 256。
16 位数二进制数的模,就是 2^16,按照十进制,它就是 65536。

二进制数的补码
求二进制数的补数,目的是往计算机里面存放。
在计算机里面,存放的数字什么的,都称为机器码;那么二进制形式的补数,也就改称为补码了。
一般情况下,都是以 8 位二进制数来讨论补码,少数也有用 16 位数的。

计算时加上正数,是不需要进行求取补数的;只有进行减法(或者加上负数),才需要对减数求补数。
补码就是按照这个要求来定义的:正数不变,负数即用模减去绝对值。

已知一个数 X,其 8 位字长的补码定义为:

      / X 0 <= X <= +127 ;正数和0的补码,就是该数字本身  [X]补 = |       \ 2^8 -|X| -128 <= X < 0 ;负数的补码,就是用 1 0000 0000,减去该数字的绝对值 例如 X = -126,其补码为 1000 0010,计算方法如下:     1 0000 0000    - 0111 1110  -----------      1000 0010 可以看出,按照补码的定义来求补码,概念十分清晰,方法、步骤也是十分简单的。 应用补码进行计算 用补码计算:83-25=58。     83  ---都变成补码,再用加法运算-->  0101 0011
  - 25  -> 1 0000 0000 – 0001 1001-> + 1110 0111
 -----          --------
    58  <--忽略进位1,结果就是正确的--[1] 0011 1010 计算结果如果超出了-128~+127的范围,结果将是错误的,这是没有办法纠正的。 应用补码进行计算,完全符合前面介绍的“用补数可把减法转换成加法”的做法,只要忽略进位(这个进位1,就是求补的时候,加进去的1 0000 0000中的1),结果就是正确的。 这些关于补数、补码的定义、方法、步骤,读者如果看懂了前面的文字,相信大家自己都可以总结出来。 那么为什么总有些网友要提出关于求取补码的问题呢? 在做而论道看来,就是因为很多教材和网文都在这个问题上“画蛇添足”。 关于补码的蛇足 补码出现后,后人又补充了不少“蛇足”:符号位、求反加一、原码、反码......。 --符号位 从这个表格中,可以看出特点:正数的最高位都是0,负数的最高位都是1。 这样一来,有人就把最高位理解成了符号位。说什么是规定的用0代表正号,......。并且郑重其事的补充说明:“符号位也参加运算”。真能忽悠!卖拐、卖车的都甘拜下风。 其实,前面说过的 补数 和 补码的定义式 里面,根本就没有什么符号位。这最高位的1、0是自然出现的,并不是由人来规定的。 --求反加一 负数补码的后面七位,也可以看出一个不完全的规律:它们和绝对值之间存在着“求反加一”的关系。 于是,又有人推出了这个不同于定义式的算法。 --原码和反码 由于使用“求反加一”来求取补码,顺便又引出了 原码 和 反码 两个垃圾概念。 其实,“求反加一”的计算方法只是适用于计算二进制形式的补数,它并不是通用的。 并且把“求反加一”用于求-128的补码,有个溢出的现象,很多人都在这里被弄瘸了很长时间。 原码和反码也只不过是“人工”进行“求反加一”时的中间过程,在计算机里面根本是不存在的,它们也就没有丝毫用处。 求取补码,就按照定义的规定,负数采用“模减去绝对值”的方法来求,这是求补数的通用方法,适合于各种进制、各种大小的数字。 不要用求反加一的方法,也就不用理会原码和反码了,也不牵涉符号位的问题。 以后的计算,也就没有必要特殊说明:“符号位一起参加运算...”,因为根本就没有什么符号位

浮点数的存储

2013年8月28日

■ 现实生活中的小数
数学中的小数,又称为实数。一般用十进制表示

例如: 3.14159265

■ 科学计算法
数学中的科学计算法许多种表示法

3.14159265 = 0.314159265 × 101

■ 计算机中浮点数的表示

在计算机中的使用科学计数法是一种“规格化计数法”。

● 规格化计数法
用科学计数法表示实数时,如果最左边的第一个数字不是0,则被称为“规格化计数法”
0.1 × 10-2 不是规格化计数法
1.0 × 10-3 则是规格化计数法

● IEEE 754 标准
IEEE 754 标准成立于1985年,80年代起所有的计算机系统均支持IEEE 754
IEEE 754 对浮点数在计算机表示方法有三个主要的规定:

对于单精度(single precision):单精度浮点数位长:32位

(1) IEEE 754 标准规定:第1位为符号位,1 代表负,0代表正
(2) 接下来用8位来表示指数部分。
(3) 接下来的23位用来表示有效数位

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
– ————— ———————————————
S 指数(8位) 有效数位 (23 位)

★ IEEE 754 考虑到利用现有的整数比较指充,对浮点数能进行快速的比较和排序,由于指数部分大小能快速反应出浮点数的大小,所以,在符号位接下来的8位用来表示指数,有效数位的大小反应出浮点数的精度。安排在最后的23位

★ 对于规格化二进制浮点示法而言,有效数位的第1位必定是1而不是0,因此,IEEE 754 规定:实际有效数位中的第1位被省去,因而,有效数位中默计含有1位。

★ 移码:除了将指数安排在有效数位前面,还不足以快速比较两个浮点数的大小,例如:

1.0 × 2 -1 在计算机中表示为:0 11111111 00000000000000000000000
这个数相当于整数的 0x7F800000

1.0 × 2 1 在计算机中表示为:0 00000001 00000000000000000000000
这个数相当于整数的 0x00800000

如果用整数比较指令,比较两个数,1.0 × 2 -1 竟然比 1.0 × 2 1 还大!

为了解决这个问题,IEEE 754 设计了一个方案:将指数加上一个常数 127
这个常数 127 被称为“移码”(biased notation)

我们再来看一看:
1.0 × 2 -1 将指数: -1 + 127 = 126 后,得出以下的二进制数:
0 01111110 00000000000000000000 也就是: 0x3F000000

1.0 × 2 1 将指数:1 + 127 = 128 后,得出以下的二进制数:
0 10000000 00000000000000000000 也就是:0x40000000

这样的话,就可以得出正确结果了。

对于双精度(double precision)浮点数来说:位长64 位
(1)IEEE 754 标准规定:第1位为符号位,1 代表负,0代表正。
(2)接下来用11位来表示指数部分。
(3)接下来的52位用来表示有效数位。

★ 双精度浮点数用52位来表示有效数位,11位表示指数位,这样提高浮点数的精度,也还提高了浮点数的取值范围。

★ 双精度的移码为 1023

例子:
1、将 -0.625 转化为计算机中的二进制数浮点数
解:
-0.625 = -5/8 = -5/23 = -101 × 2-3 = -1.01 × 2-1

符号位:1
指数位:-1 + 127 = 126
有效数位:1.01(在机器中要相应去掉默认位)

所以,在机器表示的二进制序列为:1 01111110 0100000000000000000000
相当于整数:0xBF200000

2、将如下二进制序列用十进制浮点数表示。
11000000101000000000000000000000

解:
符号位:1 是负数
指数位;10000001 = 129, 这个数要减去移码值,即:129 – 127 = 2
有效数位:01000000000000000000000 这个数要加上默认1,即得:1.01

整个序列结果为:- 1.01 × 22 = -101 = -5.0

下面的例子是按照二进制格式化输出整型、字符型以及单精度和双精度浮点型的例子:

#include<stdio.h>

/*
 *
 *fun1(char);
 fun2(int )
 fun3(float);
 fun4(double);
 *
 *
 *
 * */

void fun4(double n)
{

    int i;
    unsigned j = 0;
    char *p = (char*)&n;

    putchar(10);
    printf("double n:%lf\n",n);
    for(i = 7; i >= 0; i--)
    {
        for(j = 0x80; j != 0; j >>= 1)
        {
            if(*(p + i) & j)
            {
                putchar('1');

            }
            else{
                putchar('0'); 
            }
            if( i == 7 && j == 0x80 || i == 6 && j == 0x10)
            {
                putchar(' ');
            }
        }
    }

    putchar(10);
}



void fun3(float n)
{
    char * q = (char*)&n;
    int i;
    unsigned int j = 0;
    putchar(10);

    printf("float  n:%f\n",n);
    for(i = 3; i >= 0; i--)
    {
        for(j = 0x80; j != 0; j >>= 1)
        {
            if(*(q + i) & j)
            {
                putchar('1');
            }
            else
            {
                putchar('0');
            }
            if(i == 3 && j == 0x80 || i == 2 && j == 0x80)
            {
                putchar(' ');
            }
        }
    }



    putchar(10);
}
void fun2(int n)
{
    unsigned int j = 0x80000000;
    putchar(10);
    printf("int  n:%d\n",n);
    while(j != 0)
    {

        if(n & j)
        {
            putchar('1');
        }
        else
        {
            putchar('0');
        }

        if(j == 0x80000000 || j == 0x00800000)
        {
            putchar(' ');
        }
        j >>= 1;

    }
    putchar(10);
}
void fun1(char n)
{

    char  i = 0;
    unsigned  char  j = 0x80;

    putchar(10);
    printf("char n: %d\n",n);
    while(i < 8)
    {
        if(n & j)
        {
            putchar('1');
        }
        else
        {
            putchar('0');
        }
        j >>= 1;
        i++;
    }
    putchar(10);
}
int main(void)
{
    float x1 = 4.25;
    float x2 = -4.25; 
    double y1 = 4.25;
    double y2 = -4.25;
    int z1 = 5;
    int z2 =-5;
    char w1 = 13;
    char w2 = -13;

    fun2(z1);
    fun2(z2);

    fun1(w1);
    fun1(w2);

    fun3(x1);
    fun3(x2);

    fun4(y1);
    fun4(y2);

    putchar(10);    
    return 0;
}

浮点数的二进制表示

2013年8月28日

1.
前几天,我在读一本C语言教材,有一道例题:

#include
void main(void){
int num=9; /* num是整型变量,设为9 */
float* pFloat=# /* pFloat表示num的内存地址,但是设为浮点数 */
printf("num的值为:%d\n",num); /* 显示num的整型值 */
printf("*pFloat的值为:%f\n",*pFloat); /* 显示num的浮点值 */
*pFloat=9.0; /* 将num的值改为浮点数 */
printf("num的值为:%d\n",num); /* 显示num的整型值 */
printf("*pFloat的值为:%f\n",*pFloat); /* 显示num的浮点值 */
}

运行结果如下:

num的值为:9
*pFloat的值为:0.000000
num的值为:1091567616
*pFloat的值为:9.000000

我很惊讶,num和*pFloat在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?
要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。我读了一些资料,下面就是我的笔记。
2.
在讨论浮点数之前,先看一下整数在计算机内部是怎样表示的。
int num=9;
上面这条命令,声明了一个整数变量,类型为int,值为9(二进制写法为1001)。普通的32位计算机,用4个字节表示int变量,所以9就被保存为00000000 00000000 00000000 00001001,写成16进制就是0x00000009。
那么,我们的问题就简化成:为什么0x00000009还原成浮点数,就成了0.000000?
3.
根据国际标准IEEE 754,任意一个二进制浮点数V可以表示成下面的形式:

(1)(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
(2)M表示有效数字,大于等于1,小于2。
(3)2^E表示指数位。
举例来说,十进制的5.0,写成二进制是101.0,相当于1.01×2^2。那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。
十进制的-5.0,写成二进制是-101.0,相当于-1.01×2^2。那么,s=1,M=1.01,E=2。
IEEE 754规定,对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

5.
IEEE 754对有效数字M和指数E,还有一些特别规定。
前面说过,1≤M<2,也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
至于指数E,情况就比较复杂。
首先,E为一个无符号整数(unsigned int)。这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,E的真实值必须再减去一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。
比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
然后,指数E还可以再分成三种情况:
(1)E不全为0或不全为1。这时,浮点数就采用上面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
(2)E全为0。这时,浮点数的指数E等于1-127(或者1-1023),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
(3)E全为1。这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)。
6.
好了,关于浮点数的表示规则,就说到这里。
下面,让我们回到一开始的问题:为什么0x00000009还原成浮点数,就成了0.000000?
首先,将0x00000009拆分,得到第一位符号位s=0,后面8位的指数E=00000000,最后23位的有效数字M=000 0000 0000 0000 0000 1001。
由于指数E全为0,所以符合上一节的第二种情况。因此,浮点数V就写成:
V=(-1)^0×0.00000000000000000001001×2^(-126)=1.001×2^(-146)
显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000。
7.
再看例题的第二部分。
请问浮点数9.0,如何用二进制表示?还原成十进制又是多少?
首先,浮点数9.0等于二进制的1001.0,即1.001×2^3。
那么,第一位的符号位s=0,有效数字M等于001后面再加20个0,凑满23位,指数E等于3+127=130,即10000010。
所以,写成二进制形式,应该是s+E+M,即0 10000010 001 0000 0000 0000 0000 0000。这个32位的二进制数,还原成十进制,正是1091567616。

堆栈中的EIP EBP ESP是什么

2013年8月28日

先写个小程序:
void fun(void)
{
printf(“hello world”);
}
void main(void)
{
fun()
printf(“函数调用结束”);
}
这是一个再简单不过的函数调用的例子了。
当程序进行函数调用的时候,我们经常说的是先将函数压栈,当函数调用结束后,再出栈。这一切的工作都是系统帮我们自动完成的。
但在完成的过程中,系统会用到下面三种寄存器:
1.EIP
2.ESP
3.EBP
当调用fun函数开始时,三者的作用。
1.EIP寄存器里存储的是CPU下次要执行的指令的地址。
也就是调用完fun函数后,让CPU知道应该执行main函数中的printf(”函数调用结束”)语句了。
2.EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行fun()函数调用之前,由ESP传递给EBP的。(在函数调用前你可以这么理解:ESP存储的是栈顶地址,也是栈底地址。)
3.ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶。并且始终指向栈顶。

当调用fun函数结束后,三者的作用:
1.系统根据EIP寄存器里存储的地址,CPU就能够知道函数调用完,下一步应该做什么,也就是应该执行main函数中的printf(“函数调用结束”)。
2.EBP寄存器存储的是栈底地址,而这个地址是由ESP在函数调用前传递给EBP的。等到调用结束,EBP会把其地址再次传回给ESP。所以ESP又一次指向了函数调用结束后,栈顶的地址。
其实我们对这个只需要知道三个指针是什么就可以,可能对我们以后学习栈溢出的问题以及看栈这方面的书籍有些帮助。当有人再给你说EIP,ESP,EBP的时候,你不能一头雾水,那你水平就显得洼了许多。其实不知道我们照样可以编程,因为我们是C级别的程序员,而不是ASM级别的程序员。

公用体在格式化中的应用

2013年7月9日

公用体在格式化中或许有着很高效而又简洁的效果。可以来看个例子:

关于4个字节转换为浮点数

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
using namespace std;


int main()
{
	union {
			struct {
				unsigned char b1;
				unsigned char b2;
				unsigned char b3;
				unsigned char b4;
			} a;
			float l;
		} u;
	u.a.b1 = 0x38;
	u.a.b2 = 0x3c;
	u.a.b3 = 0xbe;
	u.a.b4 = 0x62;
	/*
	u.a.b1 = 0x62;
	u.a.b2 = 0xbe;
	u.a.b3 = 0x3c;
	u.a.b4 = 0x38;
	*/
	printf("%f\n", u.l);
}

但是有个问题就是必须得区分是大端或在小端处理器模式!