`
lovnet
  • 浏览: 6722340 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

C++与C#对比学习:预编译与编译机制(一)

 
阅读更多

源代码的翻译简介

我们知道机器其实是比较笨的,它只认识0101这样的机器语言.你用高级语言写的源代码对机器来说就像地球人看火星文一样,没法整明白的.必须要经过中间很多翻译环节.通常来说分为如下四步,实际上每一步都相当于在做翻译中的一小部分工作.

1.预编译阶段,也叫预处理.顾名思义就是在处理之前预先做些准备工作.比如你写的#include #define这样的预编译指令,还有一些注释,编译器可不认识.你得先用个预处理器把它们翻译下,让编译器可以认识才行.比如把注释去掉.把#include这样的标志也去掉,替换成引用的头文件的实际内容.

2.编译阶段.经过预处理后的源码,编译器就做啥词法分析,语法分析,如果它觉得老兄你写的代码错的有点太离谱就给个error,如果觉得源码虽然不太规范,但还不是太不靠谱就只给个warning.经过分析没有error的话最终会被翻译成等价的汇编语言.

3.汇编阶段.把第2步产生的汇编代码再翻译成0101这样的机器语言.

4.链接阶段.可能你写的代码并不是编译汇编成某一个文件,可能分散在不同的文件中.所以需要把它们链接到一起.成了最终的可执行文件,你双击下就可以运行了.这一步最终产生一个成品,前面三步产生的只是半成品.

注意事项.实际上只是便于理解像上面这样划分.虽然有时确实严格按上面四步,每一步有相应的工具来做那些工作.但有时并没有严格像上面这样按4步,但大概思想差不多.

1.)比如像C#,它没有像C++一样有一个独立的预处理器来做预处理阶段的工作,而是由编译器一起做了.实际上我们可以把上面4步全部用一个工具(编译器)做了,而不用分成4步然后用4个工具来做

2.)实际上严格来说,第4步产生了可执行文件,变成了机器能识别的机器语言就真的意味着机器能认识了.这时的可执行文件还是要基于操作系统的,只有依托于操作系统才能运行.实际上你可这样想,操作系统把底层硬件做了一个抽象.操作系统与底层硬件一起组成了一个抽象的"机器",你最终的代码能被这个"机器"识别

3.)实际上你还可以在操作系统上面再抽象一层,抽象出一个"机器",比如C#最终翻译成MSIL(微软中间语言),也是二进制串,但真正的机器可不认识.只有CLR这个"机器"能认识.另外像html标识语言,它翻译后也只有浏览器这个"机器"可以认识

不过为了便于理解,我们还是按上面说的4个步骤来分析下每一步具体做了些啥工作.先来说第一步预编译

预编译(或者说预处理)

预编译能做的事主要是下面4项

1.通过#include从其他文件里复制源程序正文来替换.我们在C++经常用它来引用头文件啊

2.通过#define定义宏,可以简单的把宏理解为把一长串字符ABC用一个很短的字符A来在代码中代替.然后预编译时又把所有的A替换回去,变成ABC.

3.条件编译,通过#define和#ifdef配合使用,把代码中不符合条件的代码块在预编译阶段全部删除掉.

4.用#progma以某种与实现有关的方式影响编译过程.比如有些警告信息你看着不顺眼,就可以通过#progma让编译器不再显示那警告信息

#include是C++中特有的,头文件也是C++中的概念.而头文件和#include在C#中没有了.那你肯定会有点迷糊,头文件起啥作用的?为啥C#中会没有?

C++头文件作用,为什么C#不需要头文件.

C++头文件作用

C++中的头文件自然是有些好处,可以很清晰的通过看一个头文件而知道一个类里面有啥成员变量和成员函数.然后可以通过引用一个头文件来使用其他类中的public成员和函数.不过其实头文件跟我们用define定义宏一样,只是方便我们而出现的.在预编译阶段全部被替换成了头文件中的实际内容.所以编译器编译的时候根本不会知道有头文件这东东存在.只有一个个的CPP文件.

在C++中,假如有有类Arwen需要用到类ABC的信息,Arwen中有函数Fun(),那必须在Arwen中用#include "ABC.h" 这样来引用头文件, 然后预编译的时候把#include "ABC.h"这一串给去掉.替换成文件ABC.h中的实际内容.比如void Fun();这些信息啊.然后呢接下来就是编译了,编译器就看你用到ABC的信息时是不是与ABC的声明吻合.例如你用ABC.Fun();函数时编译器一看Fun函数在声明中有就OK,但如果用ABC.Test();一看声明中没有,于是报错.

class ABC

{

public: void Fun();

};

类Arwen没预编译之前是下面这样

#include "ABC.h"

class Arwen

{

void CallFun() { ABC abc; abc.Fun(); }

}

预编译之后

class ABC

{

public: void Fun();

};

class Arwen

{

void CallFun() { ABC abc; abc.Fun(); }

}

C#不需要头文件的原因

namespace MyZone

{

class ABC {public Fun() { } }

}

namespace MyZone

{

classArwen {

public CallFun() {ABC abc = new ABC(); abc.Fun(); }

}

}

假如在C#,然后有类Arwen,需要用到类ABC的信息.可C#没有头文件的概念,在Arwen中用到ABC时咋整啊? C#的原理是这样的,编译的时候是先生成一些元数据(metadata),元数据里面有名称空间信息,然后每个名称空间(namespace)下面有些啥类,类的访问限制,类中有些啥字段,方法等(字段与方法等同于C++中的成员变量和成员函数).有了这些信息后编译器就可以靠这些信息判断你调用其他类时是否正确.像上面Arwen与ABC都在命名空间MyZone下面.编译器检查abc.Fun()调用正确不,会去元数据中查找MyZone下面跟类ABC相关信息.

但如果ABC如类Arwen不在同一个namespace中的话就得在Arwen前面用using ABCnamceSpace; 引用一个命名空间.实际上这和C++中用#include引用头文件的思想有点类似.C#是用using 引用命名空间.因为默认情况下编译器只会去检查与当前类相同命名空间的所有类信息.只有你用using显式引用了其他命名空间时才会也去检查那些命名空间下的类信息.

一般情况下一个项目就对应一个dll,与C++中的dll不同,C#的dll包含的信息更多,所有元数据都在dll中.而一般一个项目中也只用到一个命名空间.当然我说的是一般,实际上一个dll中可以有多个命名空间, 或者多个dll是同一个命名空间. dll是实际存在的文件.命名空间是一个抽象的概念性的东西.
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics