VC11.0编译器对C/C++代码的原始实现原理(部分)

本文主要讲的是VC编译器如何按照我们所表达的意思”编译,VC11.0的Release配置版本默认会对源码进行优化,这样的话无法还原源码和机器指令的对应关系,所以我关闭了编译器所有的优化,这也是为什么本文叫“代码的原始实现原理”的原因。本文难免有错误,欢迎指正!

为了方便查看汇编代码的函数调用关系,所以采取动态链接VC运行库,以方便加载VC库函数的符号。

这里声明一个约定:文中提到的内存地址均是16进制的。

第一部分:变量的内存分配

在介绍之前,先说明一下CPU的寄存器,我的计算机是64位的,但是为了方便,所以编译的程序是32位的,而且使用的32位调试器来进行分析的。
这里简单介绍一下常用的寄存器。 32位平台常用的CPU寄存器如下:
EAX
ECX
EDX
EBX //前面四个寄存器通常是存放临时数据
ESP //ESP是非常关键的一个寄存器,它的作用是记录栈顶的内存地址
EBP //在VC编译器编译出来的程序中,EBP的值通常作为局部变量寻址的基址
ESI
EDI
EIP //EIP用于记录程序当前执行指令所在的内存地址

上述寄存器的长度都是32位,即4字节长度,本部分着重需要弄明白ESP,EBP的用途,下面举实例做讲解:

一.局部非静态变量的内存分配

实例1

C源码:

#include <stdio.h>
int main()
{
char ch='a';
return 0;
}

反汇编结果:

00011000 >/$  55            push ebp
;将原来寄存器ebp的值入栈
00011001  |.  8BEC          mov ebp,esp
;将寄存器esp的值传送到ebp
00011003  |.  51            push ecx
;将寄存器ecx的值入栈
00011004  |.  C645 FF 61    mov byte ptr ss:[ebp-0x1],0x61
;将字符'a'的ASCII码0x61传送到内存地址为ebp-0x1的内存空间里,
;这个内存空间就是变量ch的内存空间,注意到这个内存空间是怎么
;来的,后面详细解释
00011008  |.  33C0          xor eax,eax
;VC的编译器通常将寄存器EAX作为返回值,对eax自身进行异或运算
;结果为0,于是就对应源码里面的return 0;
0001100A  |.  8BE5          mov esp,ebp
0001100C  |.  5D            pop ebp
;上面两句与函数开头的两句指令相对应,作用是恢复函数调用前寄
;存器的值,清理现场。
0001100D  .  C3            retn
;返回

大部分指令都好理解,唯一不太好理解的是“ch变量的内存空间是怎么来的?”。
下面做一个假设来模拟运行上诉指令:
假设刚进入main函数的时候,esp的值为0025F9E8,即此时栈顶的地址为0025F9E8
push ebp
这时esp的值减去4变为0025F9E4,因为寄存器ebp是4个字节长度
这里要说明一下实际压栈的原理,CPU实际的压栈操作并不是真的把数据往下面“压”,而是在栈顶的“上面”添加数据,再把esp寄存器减去相应的长度数值(栈是中的数据是越靠近栈顶,内存地址越低,所以减少esp的值就相当于“升高”栈顶),这样就“变相”地完成了“压栈”操作了。这是一种灵活的处理方法,毕竟如果真是“压”的话,要将后面的数据全部往下面移动,性能开销太大了。
mov ebp,esp
将esp现在的值传送给ebp,所以ebp保存着当前esp的值0025F9E4
push ecx
将ecx的值“压”到栈顶,此时esp-4,所以esp的值是0025F9E0,ebp的值仍然是0025F9E4
mov byte ptr ss:[ebp-0x1],0x61
此时实际上就是把'a'的ASCII码0x61保存到0025F9E4-1=0025F9E3。这个0025F9E3就是变量ch的内存地址。

好了,到此为止,看明白了么? 那句push ecx就是分配ch变量的内存的关键,其实ecx的值是无关数据,压栈的目的不是为了临时保存ecx的值,而是将栈“空”四个字节出来,即给ch分配内存空间,共分配了4个字节(0025F9E0-0025F9E3),但实际上变量ch只用了1个字节,但区区浪费3个字节无所谓啦,毕竟只是短时间占用,没什么影响的。 接下来看另一个例子。

实例2

C源码:

#include <stdio.h>
int main()
{
	int a=100;
	int b=200;
	int c=300;
	int d=400;
	return 0;
}

反汇编结果:

00EE1000 >  55              push ebp
00EE1001    8BEC            mov ebp,esp
00EE1003    83EC 10         sub esp,0x10
;将esp减去0x10,即减去16
;在实例1的时候就讲过CPU压栈的原理,这里
;将esp减去16,就是将栈顶“向上移动”16个字节
;就相当于在栈顶预留16个字节
;这16个字节就是给a、b、c、d这四个int变量分配的内存空间
00EE1006    C745 FC 6400000>mov dword ptr ss:[ebp-0x4],0x64
;a=100
;将0x64,也就是十进制的100保存到内存地址为ebp-0x4的内存空间
;这和实例1的处理方法相同,请参照实例1后面的说明
;都是用ebp的值作为基址来寻址变量内存空间的
00EE100D    C745 F8 C800000>mov dword ptr ss:[ebp-0x8],0xC8
;b=200
00EE1014    C745 F4 2C01000>mov dword ptr ss:[ebp-0xC],0x12C
;c=300
00EE101B    C745 F0 9001000>mov dword ptr ss:[ebp-0x10],0x190
;d=400
;这四句指令类似,只是通过不同的偏移来寻址到各自的内存空间
00EE1022    33C0            xor eax,eax
;将eax清零,作为返回值
00EE1024    8BE5            mov esp,ebp
00EE1026    5D              pop ebp
;这两句指令用于还原寄存器的值,使其值恢复到调用函数之前,
00EE1027    C3              retn
;返回

实例2和实例1不同的地方在于:实例1是通过对ecx压栈来分配内存,实例2是直接通过减掉寄存器esp的值来移动栈顶来分配内存。
相同的地方在于:局部非静态变量都是在栈上分配的内存空间,函数执行完以后,esp的值被还原成执行函数之前的值,就相当于释放了函数运行过程中占用的栈,这也是为什么局部非静态变量在函数执行结束后会数据会丢失的原因。
经过试验,
当函数里的局部非静态变量总大小小于等于4字节的时候,编译器会采取push ecx的方法分配这四个字节;
当函数里的局部非静态变量总大小大于4字节的时候,编译器会采取sub esp,0xXX的方法来分配这些变量的内存。
这样做的目的是减少指令长度,因为push ecx对应的机器指令只有1个字节长度,而sub esp,0xXX对应的机器指令则有3个字节。
有人又会问,局部非静态变量总大小为8字节的时候,为什么不采取连续两次push ecx的方法分配8字节内存呢?两次push ecx需要2字节,但也比3字节少啊?我个人认为这里有个平衡问题。

push ecx

CPU执行的时候,实际上将它分为两步

sub esp,0x4
mov [esp],ecx

本来这句mov [esp],ecx指令就是没有什么用处的东西,我们根本不需要保存ecx的值,我们现在需要的仅仅只是将栈顶“上移”也就是sub esp,0x4,如果两次push ecx就会做两次无用功。而且相对于访问CPU寄存器而言,访问内存效率要低得多(CPU访问内存必须经过总线),如果把这些因素考虑在内,为了达到性能和大小的平衡,两次push ecx还不如用sub esp,0x8

二.局部静态变量的内存分配

实例1

C源码:

#include <stdio.h>
int main()
{
	static int a;
	static char b;
	a=1994;
	b='X';
	return 0;
}

反汇编结果:

001A1000 >  55              push ebp
001A1001    8BEC            mov ebp,esp
001A1003    C705 20301A00 C>mov dword ptr ds:[0x1A3020],0x7CA
;将1994赋值给a
001A100D    C605 24301A00 5>mov byte ptr ds:[0x1A3024],0x58
;将'X'赋值给b
001A1014    33C0            xor eax,eax
001A1016    5D              pop ebp
001A1017    C3              retn

很明显,变量a和b的内存地址分别是0x1A3020和0x1A3024。由此看出局部静态变量所占内存空间的内存地址是固定的。

实例2

C源码:

#include <stdio.h>
int main()
{
	static int a=1994;
	static char b='X';
	return 0;
}

反汇编结果:

00961000 >  55              push ebp
00961001    8BEC            mov ebp,esp
00961003    33C0            xor eax,eax
00961005    5D              pop ebp
00961006    C3              retn

奇怪!怎么没有赋值过程啊?从反汇编结果来看在main函数里面似乎什么都没有做。确实,什么都没有做!这也是局部静态变量和局部非静态变量初始化值的方式的差异。为了继续探究,我们在原来的代码中加入一句printf()来找到这两个变量的内存地址。
修改后的C源码如下:

#include <stdio.h>
int main()
{
	static int a=1994;
	static char b='X';
	printf("%d %d",&a,&b);
	return 0;
}

反汇编结果:

011F1000 >  55              push ebp
011F1001    8BEC            mov ebp,esp
011F1003    68 04301F01     push 0x11F3004
011F1008    68 00301F01     push 0x11F3000
011F100D    68 F8201F01     push 0x11F20F8                           ; ASCII "%d %d"
011F1012 >  FF15 90201F01   call dword ptr ds:[0x11F2090]            ; msvcr110.printf
011F1018 >  83C4 0C         add esp,0xC
011F101B    33C0            xor eax,eax
011F101D    5D              pop ebp
011F101E    C3              retn

现在我们能够看到变量a和b的内存地址分别是0x11F3000和0x11F3004,我们在查看一下相应的内存(内存数据是用十六进制表示的)。

0x11F3000 >CA 07 00 00 58 00 00 00 01 00 00 00 00 00 00 00  ?..X..........

在0x11F3000处的“CA070000”就是整数1994的十六进制的“小端方式(Little-endian)”存储值,在0x11F3004处的“58”就是'X'的ASCII码。
事实上,局部静态变量的初始值是由编译器硬编码到程序中的,随着程序的启动,这些值就随即映射到相应的内存空间里面,也就是说其初始值在程序启动的时候就已经有了。

由上面两个实例也可以看出,

TYPE VAR;
VAR = VALUE;

TYPE VAR = VALUE;

所表达的意思其实并不相同。
前者是声明一个变量,然后再给它赋值;后者是声明一个初始值为多少的变量。对于局部非静态变量,两者的意义虽不同但是实现方法方法是一样的,因为局部非静态变量的初始值不能像静态变量那样,编译的时候就硬编码到程序.data段里面,局部非静态变量必须临时分配含有未知数据的内存空间,然后再赋值才能实现初始值,但是对静态变量而言,就看得出来这两种代码的差异了。
(当然,在打开编译器的优化选项以后,编译器会对源代码进行灵活处理,那样的话编译器对这两种代码的处理可能是相同的。对于开启优化选项的情况本文暂且不提,正如本文开头所说的那样,本文只是探究编译器是如何“按照我们的意思”编译程序的。开启优化选项的情况,以后会专门写一篇博文来探究)

三.全局(静态)变量的内存分配

“全局变量”本身其实也是“静态”的,但有些地方喜欢用全称——全局静态变量,为了简单,这里再声明一个约定:下文中均用“全局变量”这个术语。

实例1

C源码:

#include <stdio.h>
int a=1994;
int main()
{
	a=820;
	return 0;
}

反汇编结果:

01031000 >  55              push ebp
01031001    8BEC            mov ebp,esp
01031003    C705 00300301 3>mov dword ptr ds:[0x1033000],0x334
;将820赋值给变量a
0103100D    33C0            xor eax,eax
0103100F    5D              pop ebp
01031010    C3              retn

我们很容易就看出,变量a的内存地址为0x1033000
联想第二节所分析的,全局变量和局部静态变量一样,内存空间是“硬分配”的,其初始值也是“硬编码”的。从底层上看全局变量和局部静态变量确实是一样的,只是编译器在检查代码的时候限制了局部变量的静态访问范围而已,但他们的实现方式和工作方式都相同。

有第二节和第三节可看出,静态变量(包括全局变量和局部静态变量)所占的内存空间的内存地址是固定的,它们是由编译器硬编码到程序中的,且静态变量所占用的内存空间从程序开始运行就一直被占用。另外,局部静态变量的初始值也是由编译器硬编码到程序中的相应数据段上的,随着程序的运行,这些初始值也随即映射到相应的内存空间里面供程序使用。

事实上:
1.编译器对没有显式指定初始值的静态变量,默认是按初始值为0来处理的(这一点大多数C/C++的书都有提到)。
2.硬编码到程序数据段上的静态变量的初始值,随着程序的运行数据段上的数据被映射到内存中,他们所占用的内存空间正是这些全局变量所用的内存空间,这一切都是编译器事先“计算和设计”好的,所以静态变量的内存地址是不变的。

四.数组的内存分配

数组也是一种重要的数据结构,他就是“在一块的多个变量”,类似数学中“集合”的概念。类似但不同,不同点在于,数组的元素没有要求“互异性”,数组的元素可以是相同的值,而且数组所包含的元素的内存地址是连续的。下面我们来看一下VC是如何分配数组所占内存的。

实例1

C源码:

int main()
{
	int a[50];
	return 0;
}

反汇编结果:

01331000 >/$  55            push ebp
01331001  |.  8BEC          mov ebp,esp
01331003  |.  33C0          xor eax,eax
01331005  |.  5D            pop ebp
01331006  \.  C3            retn

怎么回事?从反汇编结果来看似乎main函数什么都没做啊?联想到第二节的实例2,因为那个地方也出现了这种情况。进而产生疑问,是不是数组也是静态分配的?为此我们也和之前一样加入一句printf()函数调用,并且向其传入数组的地址。

C源码:

#include <stdio.h>
int main()
{
	int a[50];
	printf("%d",a);
	return 0;
}

反汇编结果:

00981000 >  55              push ebp
00981001    8BEC            mov ebp,esp
00981003    81EC CC000000   sub esp,0xCC
;分配204个字节,一个int型是4个字节,数组是由50个int组成的,也就是200字节,
;而多分配的4个字节是用于下面的安全检查,这个安全检查是防止缓冲区越界,这不
;在本篇文章的讨论范围之内。
00981009    A1 00309800     mov eax,dword ptr ds:[0x983000]
0098100E >  33C5            xor eax,ebp
00981010    8945 FC         mov dword ptr ss:[ebp-0x4],eax
;ebp-0x4用于下面安全检查,本文不讨论
00981013    8D85 34FFFFFF   lea eax,dword ptr ss:[ebp-0xCC]
;将ebp-0xCC传入eax
00981019    50              push eax
;将eax压栈,作为printf()的第二个参数。也就是说ebp-0xCC就是数组的内存地址
;长度为200字节
0098101A >  68 F8209800     push 0x9820F8                            ; ASCII "%d"
0098101F >  FF15 90209800   call dword ptr ds:[0x982090]             ; msvcr110.printf
;调用printf()函数
00981025    83C4 08         add esp,0x8
00981028 >  33C0            xor eax,eax
0098102A    8B4D FC         mov ecx,dword ptr ss:[ebp-0x4]
0098102D    33CD            xor ecx,ebp
0098102F    E8 04000000     call 00981038                            ; 反汇编分.__security_check_cookie
;基于cookie的安全检查
00981034    8BE5            mov esp,ebp
00981036    5D              pop ebp
00981037    C3              retn

事实证明,刚刚的代码编译出来的程序中,局部非静态数组不是静态分配的,而是动态分配的。
虽然我们关闭了编译器的优化,但是从实际情况看,如果程序中没有用到这个数组,那么编译器会省略掉对这个数组的内存分配。但从本实例可以看出,编译器对数组的内存分配也很简单:

TYPE VAR[N];

就是分配N个TYPE型的变量而已。由于数组是按一个整体来分配的,所以其成员的内存地址是连续的。

五.结构与对象的内存分配

首先,我说明一下,为什么我将结构和对象放在一起,原因是,在C++中,结构体已经被扩展为类了,什么?没搞错吧?类和结构是一样的?是的,至少在底层来看,他们是一样的。如果你还不相信,你可以试试下面的代码:

C源码:

#include <iostream>
#include <cstdlib>

struct structa
{
public:
    void set(int x,int y);
    int add();
private:
    int a;
    int b;

};

void structa::set(int x,int y)
{
    a=x;
    b=y;
}

int    structa::add()
{
    return a+b;
}

using namespace std;
int main()
{
    structa as;
    as.set(2,3);
    cout<<as.add()<<endl;
    system("pause");
}

是不是发现编译顺利通过了?对的。

我刚刚说了C++编译器在实现对象和结构的时候,在底层上,两者没有区别,但没有说“在编译阶段他们没有区别”,其实在编译阶段的区别很简单:类的成员默认属性是private,而结构的成员默认属性是public.读者可以自己去尝试。

正是因为从底层上,C++的编译器对类(对象)和结构的处理没有区别,所以下面的分析以类(对象)为准,好了,步入正题。

实例一

C源码:

#include <cstdlib>

class classA
{
public:
    int a;
    int b;

private:
    int c;
    int d;

};

int main()
{
    classA var;
    var.a=1;
    var.b=2;
    system("pause");
}

反汇编结果:

000F1F50  /$  55            push ebp
000F1F51  |.  8BEC          mov ebp,esp
000F1F53  |.  83EC 10       sub esp,0x10
;分配16字节的内存
000F1F56  |.  8D4D F0       lea ecx,dword ptr ss:[ebp-0x10]
000F1F59  |.  E8 32F3FFFF   call 000F1290                            ;  反汇编分.000F1290
000F1F5E  |.  C745 F0 01000>mov dword ptr ss:[ebp-0x10],0x1
;将1赋值给成员a
000F1F65  |.  C745 F4 02000>mov dword ptr ss:[ebp-0xC],0x2
;将2赋值给成员b
000F1F6C  |.  68 08310F00   push 0xF3108                             ; /command = "pause"
000F1F71  |.  FF15 BC300F00 call dword ptr ds:[0xF30BC]              ; \system
000F1F77  |.  83C4 04       add esp,0x4
000F1F7A  |.  8D4D F0       lea ecx,dword ptr ss:[ebp-0x10]
000F1F7D  |.  E8 AEF3FFFF   call 000F1330                            ;  反汇编分.000F1330
000F1F82  |.  33C0          xor eax,eax
000F1F84  |.  8BE5          mov esp,ebp
000F1F86  |.  5D            pop ebp
000F1F87  \.  C3            retn

从上面结果看,进入main函数以后,首先移动栈顶分配16个字节的内存空间用来存放var对象的a、b、c、d四个整数型成员,这和分配数组的内存空间的方法差不多。还有就是public和private属性在底层根本没有体现出来,原因是这些属性只是在编译阶段检查和约束访问权限,也就是说这只是编译器在编译的时候对代码进行检查,如果发现“违规”访问,然后就报告错误并且终止编译,而编译后,在底层是没有这个约束的。

但是我们知道,数组成员的类型必须相同,结构和类成员的类型可以不同,这就意味着结构和类(对象)的成员长度可能“参差不齐”,那么这会导致什么现象呢?

实例二

C源码:

#include <cstdlib>

class classA
{
public:
short a;
int b;
short c;
int d;

};

int main()
{
classA var;
var.a=1;
var.b=2;
var.c=3;
var.d=4;
system("pause");
}

反汇编结果:

01141000  /$  55            push ebp
01141001  |.  8BEC          mov ebp,esp
01141003  |.  83EC 14       sub esp,0x14
;分配20字节内存空间
01141006  |.  A1 00301401   mov eax,dword ptr ds:[0x1143000]
0114100B  |.  33C5          xor eax,ebp
0114100D  |.  8945 FC       mov dword ptr ss:[ebp-0x4],eax
;前面4个字节用于安全检查
01141010  |.  B8 01000000   mov eax,0x1
01141015  |.  66:8945 EC    mov word ptr ss:[ebp-0x14],ax
;var.a=1;
01141019  |.  C745 F0 02000>mov dword ptr ss:[ebp-0x10],0x2
;var.b=2;
01141020  |.  B9 03000000   mov ecx,0x3
01141025  |.  66:894D F4    mov word ptr ss:[ebp-0xC],cx
;var.c=3;
01141029  |.  C745 F8 04000>mov dword ptr ss:[ebp-0x8],0x4
;var.d=4;
01141030  |.  68 B8201401   push 0x11420B8                           ; /command = "pause"
01141035  |.  FF15 90201401 call dword ptr ds:[0x1142090]            ; \system
0114103B  |.  83C4 04       add esp,0x4
0114103E  |.  33C0          xor eax,eax
01141040  |.  8B4D FC       mov ecx,dword ptr ss:[ebp-0x4]
01141043  |.  33CD          xor ecx,ebp
01141045  |.  E8 04000000   call 0114104E                            ;  反汇编分.0114104E
;安全检查
0114104A  |.  8BE5          mov esp,ebp
0114104C  |.  5D            pop ebp
0114104D  \.  C3            retn
注意,在32位系统下,int是4字节,short是2字节,所以按道理a、b、c、d四个成员合起来应该是12字节,从上面反汇编的结果看总共分配了20字节内存,除去用于安全检查的4字节,为什么分配了16字节的内存呢?这里有一个内存对齐的问题,我的VC11(VS2012)默认是4字节对齐,所以会进行如下处理:
a是short型,长度为2字节,所以直接分配2字节用来存放a;此时总共分配了2字节
b是int型,长度为4字节,但是由于要按4字节进行内存对齐,而前面的a只占用了2字节,不满足4的倍数,所以要在a后面分配2字节,这样才能满足内存对齐,然后再分配4字节来存放b;此时总共分配了8字节
c是short型,长度为2字节,由于前面总共分配了8字节,满足内存对齐,所以直接分配2字节来保存c;此时总共分配了10字节;
d是int型,长度为4字节,和b类似,需要先补齐2字节,然后再分配4字节用于保存d;此时总共分配了16字节。
16字节就是这么来的,多分配的就是用于内存对齐了。至于为什么要内存对齐,是为了提高CPU工作效率,详情请自行百度。

(由于之前误删了分析报告,我又懒得重新写一份了,所以本文不再更新)

PPTP VPN的MMS和MTU问题

问题:在linux上面用架设了PPTP VPN,但连上以后大部分网站访问速度极慢,几乎无法访问。
解决:修改MSS值
iptables -A FORWARD -p tcp --syn -s XXX.XXX.XXX.XXX/XX -j TCPMSS --set-mss 1372

原因分析:
1.断开vpn的情况下:
ping -f -l XXXX www.baidu.com

一步一步测试(XXXX为MTU大小,可以从1500开始,逐渐减小,直到可以ping通)
我们可以得到可以ping通的MTU最大为1472;

2.连接vpn的情况下:

ping -f -l XXXX www.baidu.com

一步一步测试(XXXX为MTU大小,可以从1500开始,逐渐减小,直到可以ping通)
我们可以得到可以ping通的MTU最大为1372;

3.连接vpn,在服务器上用
netstat -i
查看接口,得到

Kernel Interface table
Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 1500 0 2850923 0 0 0 2675804 0 0 0 BMRU
lo 16436 0 36427 0 0 0 36427 0 0 0 LRU
ppp0 1396 0 177 0 0 0 96 0 0 0 MOPRU

可知ppp的最大mtu为1396,当然,对应的mss应为(mtu-20字节的IP头部-20字节的TCP 头部=)1356

用VS2012 编译 Qt5.0.0 正式版

Qt5.0发布了,我也不知道多了什么东西,官方文档也懒得去看了,用多了自然就知道了,嘿嘿。。

下面来讲解一下用VS2012编译Qt5.0.0正式版的过程:

1. 下载Qt5.0:
http://releases.qt-project.org/qt5/5.0.0/single/qt-everywhere-opensource-src-5.0.0.zip

2. 下载Perl
32位:http://strawberry-perl.googlecode.com/files/strawberry-perl-5.16.2.1-32bit.msi
64位:http://strawberry-perl.googlecode.com/files/strawberry-perl-5.16.2.1-64bit.msi

Python
32位下载地址:http://www.python.org/ftp/python/2.7.3/python-2.7.3.msi
64位下载地址:http://www.python.org/ftp/python/2.7.3/python-2.7.3.amd64.msi

3. 在开始菜单的Microsoft Visual Studio 2012中的Visual Studio Tools里面找到VS2012 x86 Native Tools Command Prompt 批处理并以管理员身份运行它.

4. 在上一步打开的批处理中输入(注意把“Qt安装目录”替换成你实际的Qt安装目录,把“Qt所在磁盘盘符”替换为你的Qt安装目录所在磁盘盘符):

cd Qt的configure.bat文件目录
Qt所在磁盘盘符:

比如,我的Qt源码在E:\Soft\Qt\Qt5.0.0\5.0.0\Src\qtbase 我就输入:

cd E:\Soft\Qt\Qt5.0.0\5.0.0\Src\qtbase
E:

5.配置编译选项,输入

configure -opensource -platform win32-msvc2012 -fast -mp -debug-and-release -opengl desktop 

6.开始编译,输入
nmake -i

OK,等待编译完成吧!

Linux软连接和硬链接

1.Linux链接概念
Linux链接分两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link)。默认情况下,ln命令产生硬链接。

【硬连接】
硬连接指通过索引节点来进行连接。在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在Linux中,多个文件名指向同一索引节点是存在的。一般这种连接就是硬连接。硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。

【软连接】
另外一种连接称之为符号连接(Symbolic Link),也叫软连接。软链接文件有类似于Windows的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。

2.通过实验加深理解
[oracle@Linux]$ touch f1 #创建一个测试文件f1
[oracle@Linux]$ ln f1 f2 #创建f1的一个硬连接文件f2
[oracle@Linux]$ ln -s f1 f3 #创建f1的一个符号连接文件f3
[oracle@Linux]$ ls -li # -i参数显示文件的inode节点信息
total 0
9797648 -rw-r--r-- 2 oracle oinstall 0 Apr 21 08:11 f1
9797648 -rw-r--r-- 2 oracle oinstall 0 Apr 21 08:11 f2
9797649 lrwxrwxrwx 1 oracle oinstall 2 Apr 21 08:11 f3 -> f1

从上面的结果中可以看出,硬连接文件f2与原文件f1的inode节点相同,均为9797648,然而符号连接文件的inode节点不同。

[oracle@Linux]$ echo "I am f1 file" >>f1
[oracle@Linux]$ cat f1
I am f1 file
[oracle@Linux]$ cat f2
I am f1 file
[oracle@Linux]$ cat f3
I am f1 file
[oracle@Linux]$ rm -f f1
[oracle@Linux]$ cat f2
I am f1 file
[oracle@Linux]$ cat f3
cat: f3: No such file or directory

通过上面的测试可以看出:当删除原始文件f1后,硬连接f2不受影响,但是符号连接f1文件无效

3.总结
依此您可以做一些相关的测试,可以得到以下全部结论:
1).删除符号连接f3,对f1,f2无影响;
2).删除硬连接f2,对f1,f3也无影响;
3).删除原文件f1,对硬连接f2没有影响,导致符号连接f3失效;
4).同时删除原文件f1,硬连接f2,整个文件会真正的被删除。

 

本文转载自:http://itech.cnblogs.com/

在LNAMP上的eAccelerator1.0与Xcache3.0性能对比测试

今天闲来无事,用自己的VPS作为测试平台,测试Xcache3.0和eAccelerator1.0哪个性能更好.第一次做这种事情,做得不够专业请指正,呵呵.

处理器:Intel(R) Xeon(R) CPU E31240 @ 3.30GHz × 2
内存:1GB
测试工具:使用Apache Benchmark作为压力测试工具,并发数10,请求数5000
软件:nginx1.2.6+apache2.2.23+mysql5.5.29+ZendEngine2.3.0+ZendGuardLoader3.3
测试网站:新建phpwind9.0论坛作为测试页面(为了避免被恶意攻击,测试结果隐藏了Hostname)

 

不使用eAccelerator1.0和Xcache3.0,测试结果:

Server Software: nginx/1.2.6
Server Hostname: ***.***.***
Server Port: 80

Document Path: /phpwind/
Document Length: 10903 bytes

Concurrency Level: 10
Time taken for tests: 283.546 seconds
Complete requests: 5000
Failed requests: 0
Write errors: 0
Total transferred: 57323498 bytes
HTML transferred: 54515000 bytes
Requests per second: 17.63 [#/sec] (mean)
Time per request: 567.091 [ms] (mean)
Time per request: 56.709 [ms] (mean, across all concurrent requests)
Transfer rate: 197.43 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 3
Processing: 308 567 31.1 564 699
Waiting: 300 517 29.1 515 642
Total: 308 567 31.1 564 699

Percentage of the requests served within a certain time (ms)
50% 564
66% 577
75% 585
80% 590
90% 607
95% 621
98% 638
99% 650
100% 699 (longest request)

 

只使用eAccelerator1.0,测试结果:

Server Software: nginx/1.2.6
Server Hostname: ***.***.***
Server Port: 80

Document Path: /phpwind/
Document Length: 10903 bytes

Concurrency Level: 10
Time taken for tests: 247.201 seconds
Complete requests: 5000
Failed requests: 0
Write errors: 0
Total transferred: 57323978 bytes
HTML transferred: 54515000 bytes
Requests per second: 20.23 [#/sec] (mean)
Time per request: 494.402 [ms] (mean)
Time per request: 49.440 [ms] (mean, across all concurrent requests)
Transfer rate: 226.46 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 4
Processing: 319 494 35.7 491 688
Waiting: 312 454 34.4 451 639
Total: 319 494 35.7 491 688

Percentage of the requests served within a certain time (ms)
50% 491
66% 505
75% 514
80% 520
90% 541
95% 559
98% 582
99% 598
100% 688 (longest request)

 

只使用Xcache3.0,测试结果:

Server Software: nginx/1.2.6
Server Hostname: ***.***.***
Server Port: 80

Document Path: /phpwind/
Document Length: 10903 bytes

Concurrency Level: 10
Time taken for tests: 1560.616 seconds
Complete requests: 5000
Failed requests: 0
Write errors: 0
Total transferred: 57323430 bytes
HTML transferred: 54515000 bytes
Requests per second: 3.20 [#/sec] (mean)
Time per request: 3121.232 [ms] (mean)
Time per request: 312.123 [ms] (mean, across all concurrent requests)
Transfer rate: 35.87 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 1
Processing: 1702 3120 183.2 3098 4918
Waiting: 1657 2976 175.0 2956 4745
Total: 1702 3120 183.2 3098 4920

Percentage of the requests served within a certain time (ms)
50% 3098
66% 3166
75% 3212
80% 3249
90% 3361
95% 3453
98% 3578
99% 3634
100% 4920 (longest request)

结果总结

结果令我很惊讶!!不知道是我的XCache3.0设置问题还是怎么回事,XCache3.0很慢啊,比不用还要慢!?!?而且在测试期间CPU平均负载达到了10!?!?但愿是我设置问题。。。自己先琢磨一阵,如果找到哪里出问题了,再来更新。。。。

就目前结果分析,eAccelerator1.0对LNAMP的加速效果明显。在并发数为10的情况下处理5000个请求,如果不用eAccelerator1.0,则要花费283.546秒,使用了以后,花费的时间减少到了247.201秒,足足减少了近40秒。

本次测试并发连接数为10,请求数为5000,就能有40秒差距,相信如果并发连接数更多,总的连接数更多,eAccelerator1.0的加速效果会更明显。所以对大流量高并发连接的网站来说,加速效果绝对是很给力的!

而对于XCache3.0。。。。我暂时不做评论。。。。也许是XCache3.0和ZendGuard3.3冲突了,这里先保留测试结果,等我琢磨透了再做评论。。。。

事实证明,XCache3.0和ZendGuard3.3并没有冲突,我把ZendGuard3.3禁用了,结果XCache3.0还是那样。。。

对于这次XCache3.0测试结果这么奇怪,为此我把我使用的XCache3.0的设置贴上来,欢迎指正:

[XCache]
extension = "/usr/local/php5/lib/php/extensions/no-debug-non-zts-20090626/xcache.so"
xcache.admin.auth = On
xcache.admin.user = "xcache"
xcache.admin.pass = "8e6867a5d05144cf4761d6481fc674a8"
xcache.size = 64M
xcache.shm_scheme = "mmap"
xcache.count = 4
xcache.slots = 8K
xcache.ttl = 0
xcache.gc_interval = 0
xcache.var_size = 16M
xcache.var_count = 4
xcache.var_slots = 8K
xcache.var_ttl = 0
xcache.var_maxttl = 0
xcache.var_gc_interval = 300
xcache.test = Off
xcache.readonly_protection = Off
xcache.mmap_path = "/dev/zero"
xcache.coredump_directory = ""
xcache.cacher = On
xcache.stat = On
xcache.optimizer = On