02 January 2008

Stack and heaps

Very good article on stack and heaps from CSDN blog.
 
堆和栈的区别

一、预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456\0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}


二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在栈中的。


2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会 遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内 存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大 小,系统会自动的将多余的那部分重新放入空闲链表中。

2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数 据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总 之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。


2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

2.5堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

2.6存取效率的比较

char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。


2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。 (经典!)

CS truth and illussion

From CSDN :

幻象与真实--计算机的本质 

一、计算机的本质
计算机的本质是一个复杂的数字电路,数字电路只能处理和输出高低两种电位组成的信号,所以计算机只认识高低两种电位状态!
任 何人类的观念(比如数字,字符,图像等),在计算机里都被转换成这两种状态,再通过硬件电路或者程序的方式,使对这些状态的输出,符合这些观念本身应有的 规律和形式,因为人看不到内部的转换和处理,只看到外部输出的样子,于是,在人看来,计算机可以完成多种多样的任务,相当神奇!
计算机能够完成多种任务的两大基石:
1.编码:用计算机的高低电位对应外部事物的各种状态。
2.输出:以外部事物的样子把编码处理后的结果展示出来。(可以通过硬件电路也可以通过软件程序)
编码的概念:
用少量简单基本的符号,选用一定的组合规则,以表示出大量复杂多样的信号。基本符号的种类和这些符号的组合则是一切信息编码的两大要素,例如用10个阿拉伯数码表示数字,用26个英文字母表示英文词汇等,这就是编码的典型案例。计算机中,广泛采用01两个基本符号组成的基2,或称二进制码。(一般用高低两个电位表示10
--《计算机组成与结构》
二、具体事例
1.字符
编码:以ASCII字符集编码为例,将数字、大小写字母、标点符号、控制字符等128个符号,对应到 0x0 0x7e 上。也就是说用 0x0 - 0x7e 128个状态编码了128个符号。
输出:早期的计算机通过符号显示器这一硬件来将字符显示出来。比如当接收到计算机发过来的一个字节 0x58 时,crt将根据0x58指向的字符rom中的高低电位控制电子束的开关,在屏幕上组成图形“X”。
2.数值
编码:数值是表示数量多少的数据。对数值数据的编码,计算机中有:原码,补码,反码。
处理,计算机的运算电路,根据数值编码方案设计,使输入状态经过电路处理后得到的输出状态符合加减乘除等运算规则。比如,输入 ▂ 和 ▂ 输出 █ █ ,符合 10 + 01 = 11
输出:将结果数字转换成ascii编码,送字符显示设备。比如将 5 )转换成ascii字符 5 ▂ ▂ █ █ ▂ █ ▂ █),然后送显卡画图。
3.图像
编码:将点的位置和颜色等信息,对应到一定的高低电位序列,实现编码。
输出:显示设备根据点的信息,在显示器上输出一个点,大量的点按一定规则排列,形成人们观念中的图像。
三、一个重要理念:计算机是一部状态机,不是数字机。
计算机不认识数字,只认识高低两个状态。
数字只表示高低电位的排列状态。比如,0xb8 ,它并不代表数量,只代表电位排列状态,化成二进制为:10111000 ,就是代表: █ █ █ ▂ 这一状态。
自然界中数字的概念:
表示事物个数的符号。
进制的概念:
数学上的进位制本来是人们为了计数和运算的方便而约定的,约定逢二进一,就是二进制,约定逢三进一,就是三进制,依次类推。不同的进位制,除了繁简的差异外,没有任何本质上的区别。也就是说,不同的进制,只是自身的形式不同,其表示数目的本质是一样的,比如二进制100与十六进制4 都表示一样的数目,他们本质是一样的,仅仅形式不同。以计算机举例,内存中有 █ █ ,你可以用二进制的概念叫他为 1011 ,亦可以以十六进制叫他为 b ,也可以以十进制叫他为 11 。无论怎么叫,他内存中的状态始终不变。
因为计算机里只有高低两种状态,所以数字无论是二进制还是十六进制,只是这种状态排列的一种表示方式。在这里数字已经失去了它本来的表示数量功能的意义了,取而代之,这里表示电位排列状态。
我比较习惯把高低电位用符号表示,而不用01.因为用数字0 1 来表示高低电位,潜在的意思是:这个东西是数字,而数字是符合运算规则的。其实,计算机内部很多东西都不是数字,比如mov指令: █ █ █ ▂ ,它并不代表一个数值,只是一个cpu能识别的电位序列信号。如果用0 1 来表示就是,10111000 ,看上去就是一个数字。还有,是否符合运算规则是内部电路来决定的,如果电路设计成 输入 ▂ 和 ▂ 输出 █ █ ,那么就符合加法运算,但如果设计成输出 ▂ ▂ 呢,再表示成输入:10 01 ,输出:00 就比较别扭了。
如果克服这些障碍,把 0 1 仅仅看成是计算机的两种状态,那么表示起来还是很方便的,因为这时不仅可以用 0 1 ,还可以转化成更方便的十六进制,象上面那条 mov 指令就可以表示成 10111000 或者 十六进制 b8 。但要记住,10111000 也好 b8 也好,只是内存中高低电位状态分布的一种表示,并不表示数量。
知道这些,就容易理解:汇编程序在汇编过程中,直接查表把ASCII 码组成的 mov 汇编成 0xb8,这里如果把 0xb8 看成一个数字,就容易迷惑,把它想象成一个高低电位分布的状态,就容易理解了。
四、ASCII 里的数字字符,与真正的数字,有时让人迷惑。
以无符号整数5为例,在计算机屏幕上看到的5,与计算机内部以原码表示的数值5是不同的。在计算机屏幕上显示的5是一个字符,它以ASCII编码,在计算机内部的电位序列为: ▂ ▂ █ █ (二进制 0011 0101 十六进制0x 35)。
而计算机内部原码表示的数字 5 电位序列为:▂ ▂ ▂ ▂ ▂ (二进制 0000 0101 十六进制0x 5)。
就通常的输入手段来说,通过键盘,在编辑器里输入,不能手工输入机器数 5 ,因为编辑器程序把键盘输入的扫描码转换成了ascii编码,但老式计算机可以通过开关,打孔等输入方式输入机器数。
要想得到机器数5 ,必须借助编译器等第三方程序。比如汇编语句“ db 5 ,这里的 5 仍然是一个ASCII 码,通过汇编程序处理后,才在内存中得到真正的机器数 5
c语句“int x=5这里的5也是ASCII ,编译后才是真正的5
计算机语言的源程序,都是ASCII码组成的,尽管如此,他们还是会提供一些语法,以便确定编译后,你到底需要ASCII还是真实的数字,比如c char x = 5;与 char x = 5’;
总之,人眼可以看到的数字,就是以ASCII形式编码的字符,而真正数字的机器码,人眼是看不到的。一般计算机语言都会提供一种语法,来确定最后编译得到的是哪种形式的编码。
五、汇编与反汇编
汇编过程:
语句“ mov ax 5 ”在机器内部的表示为一堆ASCII编码:
符号形式 二进制 十六进制
m▂ █ █ ▂ █ █ ▂ █ 01101101 6d
o▂ █ █ ▂ █ █ █ █ 01101111 6f
v▂ █ █ █ ▂ █ █ ▂ 01110110 76
空格( ▂ ▂ █ ▂ ▂ ▂ ▂ ▂ 00100000 20
a▂ █ █ ▂ ▂ ▂ ▂ █ 01100001 61
x▂ █ █ █ █ ▂ ▂ ▂ 01111000 78
,( ▂ ▂ █ ▂ █ █ ▂ ▂ 00101100 2c
5▂ ▂ █ █ ▂ █ ▂ █ 00110101 35
经过汇编程序得到cpu可以运行的真实的指令:
符号形式: █ █ █
二进制形式:101110000000010100000000
十六进制形式:0x b80500
反汇编过程,这里只反汇编为16进制数表示的结果:
还是这个语句:
█ █ █
以半字节为单位,转换为16进制数的ASCII
█ █ 0x b 转换为ASCII 编码的 “b0x62 为:█ █
0x 8 转换为ASCII 编码的 “8 0x38为: █ █ █ ▂ ▂
0x 0 转换为ASCII 编码的 “00x30为:▂ ▂ █ █
█ 0x 5 转换为ASCII 编码的 “50x35为:▂ ▂ █ █
0x 0 转换为ASCII 编码的 “00x30为:▂ ▂ █ █
0x 0 转换为ASCII 编码的 “00x30为:▂ ▂ █ █
把这些反汇编后的数据送到显存,屏幕上就显示出ASCII b80500 了。

CS Abstraction

一.万事万物皆抽象

1. 人的智力是有限的,世界是越来越复杂的,人之所以能够用有限的智力建设并操控越来越复杂的世界靠的是层层抽象。

2. 原理:(建造大楼为例)

a. 最原始的状态:人的所有精力都放在研究制造砖头上,此时没有也不可能顾及如何造大楼(因为人的智力是有限的)

b. 砖头造出来后,人就不再考虑砖头,把砖头当作原料进行更高一层的抽象,开始把所有精力放在用砖头造墙上。

c. 墙造出来后,人就不再考虑墙,把墙当作原料进行更高一层的抽象,开始把所有精力放在用墙造房间上。

………………

最后造成大楼。



计 算机科学,层的概念特别突出(与其自身复杂性有关),例如:汇编层、高级语言层…,操作系统层、网络层…,每一层都有自己关心的问题、对象及处理方法,并 对低一层的结论成果等直接拿来用,而不考虑其构成(那是低一层次要做的事)。面向过程编程中的自上而下层层分解的方法,也属于此范围。

每一个单独的层面如果仍十分巨大的话,还可以按逻辑功能把这一层分成一个个独立的模块,使思维规模进一步缩小。比如在操作系统层面,可以分成:引导模块、中断模块、进程处理模块。。。

不按照每一层面每一模块上特定的规则处理问题,企图越过层面和模块办事,必然会引起混乱。

进一步说明:在做操作系统时,主要考虑的是机器的组成,工作方式,指令集,在操作时也主要使用汇编和c这样的底层、细微的工具。高层面的概念不用考虑。

而在操作系统的基础上做应用开发时,主要考虑的是系统提供的功能调用,问题对象的描述等。使用的工具也是更高级的,如object pascal,c++,basic,python等等。低层次的概念如寄存器、内存地址等概念不需要考虑(不一个层级,属于低层)。

问题的粒度不同,使用的知识与工具也不相同。

不可纠缠于细节,应该在一个层次上熟练以后,向更高级的层面延伸。如在汇编层面掌握以后,应该用汇编的知识开发操作系统和高级语言编译器(如c),然后向操作系统和高级语言层迈进,在这个层面熟练以后,应该开发组件和解释型语言,然后向这个层面进军。




当你被各种新奇的概念、瑰丽的界面和无休止的名词弄的筋疲力尽的时候,闭上眼睛,想象一下内存中机械的、冰冷的一条条高高低低的电位指令,想想地址、寄存器、callmovinout指令,你会发现:所有概念都是人自己创造的,真实的世界原来是那么的单纯明了。

·高级语言如:cc++java,都是假的。高级的概念如:段、对象、组件都是假的。他们最终都要变成内存中一个个机器指令(电信号)。

·闭上眼睛,想象下操作系统、dll模块、编译器、链接器、载入器、各个api函数、在内存中都是一块一块的指令集合。你编的程序进行编译、链接、装载、系统调用时,其实就是在这些指令块里面callret去,就是跟这些地址打交道。看看c函数编译出来的汇编吧,就是push,然后call

·当你试图弄清一个层面的问题时,往往需要借助更深层面的知识。比如c语言的函数其实就是汇编里的几个pushcall

·程序的本质在“序”。几个简单的有限的东西通过不同的排列(序),可以构成复杂而强大的东西。比如26个字母构成英语、简单的机器指令构成缤纷的软件。(这种思想是朴实而自然的,比如简单的砖头和泥灰可以盖成各式各异的建筑,简单的原子构成复杂的世界。古代有“道生一,一生二,二生三,三生万物”的说法,可见,图灵的通用计算机思想也没有什么新奇之处。)

·计算机学科的基石。

思想方面:

1. 协议。就是大家都遵从的一种约定,这样才能把力量用在一处。数据结构也是协议。

2. 模块化。把能复用的东西,抽出来,避免重复劳动。子程序(过程,函数),就是最好的例子。

·人应该做有创造性的工作,单调重复乏味的事情让计算机去做。

About pointers of C

good article on c pointer.

本质啊本质:指针的本质

c与机器联系紧密,抽象程度不是太高,所以机器层面的地址也拿过来了,改名叫指针。
遇到指针,要读成“地址变量”,其实指针就是一个存放整数(地址就是一个整数)的变量而已,多少级都是如此。
一、定义一个指针 int * p 与访问指针指向的变 * p
这两个语句里面的 * ,是两个不同的符号,要区别对待,不要把他看成一样的东西。第一个*表示:要定义变量p是个存放地址的变量。第二个*表示:要寻址了,要找p里存放的地址指向的变量了。
二、语句int *p;有三个意思:
1. p是一个变量,用来存放地址。
2. 需要几次寻址才能找到最终的那个变量(这与有几颗*有关)。
3. 最终指向的那个变量是什么类型的(这里就是 int )。
这里第一个意思最重要,看到一个定义要首先想到这一点。其他两个基本上是编译器检查时需要的信息,在类型转换,赋值时稍微注意下就ok了。
三、当看到:
int *p;
char ****p;
float ************************************p;
等等时,脑子里第一个概念就是:p 是一个地址变量,用来存放地址的。
当看到:
**p;
*p;
****************p;
等等时,脑子里第一个概念就是:要找地址p指向的变量了。
四、见过*连用,如 int ***p;或 **p 但从没见过 & 连用的,如 &&x;这是错的,&只能一个变量一个变量的取地址,如:
int x=0;
int *p=&x;
int **p2=&p;
五、char ***cc;
int *******ii;
他们的共同点: cc ii 都是一个地址变量,用来存放地址,32位机器的地址是32位,所以他们都占4个字节。
六、多重指针之所以让人迷惑,在于它间接了好多次,比如 int ***p;好像c里面有什么神秘的东西,可以一次存储下那个变态的多重指针,然后还可以通过某种神秘的方式,一下子找到那被间接了好多次的变量。其实,从编译后的汇编来看,p就是一个普通的单一的变量,而寻址的过程也是简单的重复而已。具体看下面的分析:
int main()
{
int i;
int *p1;
int **p2;
int ***p3;
int e;
i=1;
p1=&i;
p2=&p1;
p3=&p2;
e = ***p3 ;
}
_i$ = -16
_p1$ = -8
_p2$ = -12
_p3$ = -20
_e$ = -4
……
mov DWORD PTR _i$[ebp], 1
lea eax, DWORD PTR _i$[ebp]
mov DWORD PTR _p1$[ebp], eax
lea ecx, DWORD PTR _p1$[ebp]
mov DWORD PTR _p2$[ebp], ecx
lea edx, DWORD PTR _p2$[ebp]
mov DWORD PTR _p3$[ebp], edx
mov eax, DWORD PTR _p3$[ebp]
mov ecx, DWORD PTR [eax]
mov edx, DWORD PTR [ecx]
mov eax, DWORD PTR [edx]
mov DWORD PTR _e$[ebp], eax
看下绿色的地方,所谓的复杂的 三重指针p3,编译后就是一条简单的赋值语句而已。再看下红色标注的地方,***p3,编译后成了那5条简单的语句(而确切的说是那5条中间的3条,头尾都不算在间接之内),就是一个简单的重复 mov 而已啦。有什么复杂的?!
七、“本来面目”
当碰到十分复杂的指针声明时,如:int ******p; 脑子里只有一个感念,p是一个地址变量,用来存放地址。(这就是他编译后的本来面目)心中默念:100级指针,也是指针,就是一个存放整数(地址就是整数)的变量而已,怕你个p啊。

About dog

From CSDN, very impressive.
 薪酬与绩效(转载)     

一条猎狗将兔子赶出了窝,一直追赶他,追了很久仍没有捉到。
  牧羊看到此种情景,讥笑猎狗说“你们两个之间小的反而跑得快得多。“
  猎狗回答说:“你不知道我们两个的跑是完全不同的!我仅仅为了一顿饭而跑,他却是为了性命而跑呀!“
  .....................目标................................
  
  
  二
  
   这话被猎人听到了,猎人想:猎狗说的对啊,那我要想得到更多的猎物,得想个好法子. 于是,猎人又买来几条猎狗,凡是能够在打猎中捉到兔子的,就可以得到几根骨头,捉不到的就没有饭吃.这一招果然有用,猎狗们纷纷去努力追兔子,因为谁都不 愿意看着别人有骨头吃,自已没的吃.就这样过了一段时间,问题又出现了.大兔子非常难捉到,小兔子好捉.但捉到大兔子得到的奖赏和捉到小兔子得到的骨头差 不多,猎狗们善于观察,发现了这个窍门,专门去捉小兔子.慢慢的,大家都发现了这个窍门.猎人对猎狗说:最近你们捉的兔子越来越小了,为什么?猎狗们说: 反正没有什么大的区别,为什么费那么大的劲去捉那些大的呢?
  .............................动力.........................
  
  
  三
  
   猎人经过思考后,决定不将分得骨头的数量与是否捉到兔子挂钩,而是采用每过一段时 间,就统计一次猎狗捉到兔子的总重量.按照重量来评价猎狗,决定一段时间内的待遇.于是猎狗们捉到兔子的数量和重量都增加了.猎人很开心. 但是过了一段时间,猎人发现,猎狗们捉兔子的数量又少了,而且越有经验的猎狗,捉兔子的数量下降的就越利害.于是猎人又去问猎狗.
  猎狗说“我们把最好的时间都奉献给了您,主人,但是我们随着时间的推移会老,当我们捉不到兔子的时候,您还会给我们骨头吃吗?“
  ........................长期的骨头........................
  
  
  四
  
   猎人做了论功行赏的决定.分析与汇总了所有猎狗捉到兔子的数量与重量,规定如果捉到的兔子超过了一定的数量后,即使捉不到兔子,每顿饭也可以得到一定数 量的骨头.猎狗们都很高兴,大家都努力去达到猎人规定的数量.一段时间过后,终于有一些猎狗达到了猎人规定的数量.这时,其中有一只猎狗说:我们这么努 力,只得到几根骨头,而我们捉的猎物远远超过了这几根骨头.我们为什么不能给自己捉兔子呢?“于是,有些猎狗离开了猎人,自己捉兔子去了.
  .............................骨头与肉兼而有之...........
  
  
  五
  
   猎人意识到猎狗正在流失,并且那些流失的猎狗像野狗一般和自己的猎狗抢兔子.情况变得越来越糟,猎人不得已引诱了一条野狗,问他到底野狗比猎狗强在那 里。野狗说:“猎狗吃的是骨头,吐出来的是肉啊!”,接着又道:“也不是所有的野狗都顿顿有肉吃,大部分最后骨头都没的舔!不然也不至于被你诱惑。” 于是猎人进行了改革,使得每条猎狗除基本骨头外,可获得其所猎兔肉总量的n%,而且随着服务时间加长,贡献变大,该比例还可递增,并有权分享猎人总兔肉的 m%。就这样,猎狗们与猎人一起努力,将野狗们逼得叫苦连天,纷纷强烈要求重归猎狗队伍。
  
  
  故事还在继续................
  
  
  --------------------只有永远的利益,没有永远的朋友--------------
  
   日子一天一天地过去,冬天到了,兔子越来越少,猎人们的收成也一天不如一天。而那些服务时间长的老猎狗们老得不能捉到兔子,但仍然在无忧无虑地享受着那 些他们自以为是应得的大份食物。终于有一天猎人再也不能忍受,把他们扫地出门,因为猎人更需要身强力壮的猎狗。。。。。
  
  --------------------- Birth of MicroBone Co. --------------
  
   被扫地出门的老猎狗们得了一笔不菲的赔偿金,于是他们成立了MicroBone公司。他们采用连锁加盟的方式招募野狗,向野狗们传授猎兔的技巧,他们从 猎得的兔子中抽取一部分作为管理费。当赔偿金几乎全部用于广告后,他们终于有了足够多的野狗加盟。公司开始赢利。一年后,他们收购了猎人的家当...
  
  ------Development of MicroBone Co. -----------------------
  
   MicroBone公司许诺给加盟的野狗能得到公司n%的股份。这实在是太有诱惑力了。这些自认为是怀才不遇的野狗们都以为找到了知音:终于做公司的主 人了,不用再忍受猎人们呼来唤去的不快,不用再为捉到足够多的兔子而累死累活,也不用眼巴巴地乞求猎人多给两跟骨头而扮得楚楚可怜。这一切对这些野狗来 说,这比多吃两根骨头更加受用。于是野狗们拖家带口地加入了MicroBone,一些在猎人门下的年轻猎口也开始蠢蠢欲动,甚至很多自以为聪明实际愚蠢的 猎人也想加入。好多同类型的公司象雨后春笋般地成立了,BoneEase, Bone.com, ChinaBone....一时间,森林里热闹起来。
  
  ------------------- F4 的诞生 ----------------------------
  
   猎人凭借出售公司的钱走上了老猎狗走过的路,最后千辛万苦要与MicroBone公司谈判的时候,老猎狗出人意料的顺利答应了猎人,把 MicroBone公司卖给了猎人。老猎狗们从此不再经营公司,转而开始写自转《老猎狗的一生》,又写:《如何成为出色的猎狗》,《如何从一只普通猎狗成 为一只管理层的猎狗》《猎狗成功秘诀》《成功猎狗500条》《穷猎狗,富猎狗》,并且将老猎狗的故事搬上屏幕,取名《猎狗花园》,四只老猎狗成为了家喻户 晓的明星F4. 收版权费,没有风险,利润更高。
  
  ps: 干活的总是拿得少的,拿得多的都是不干活的。