从编译,执行过程理解c#
上节我们说过C#所开发的程序源代码并不是编译成能够直接在操作系统上执行的二进制代码。与Java类似,它被编译成为中间代码,然后通过.NET Framework的虚拟机——被称之为通用语言运行时(CLR)执行。所有的.Net编程语言都被编译成这种被称为MSIL(Microsoft Intermediate Language )的中间代码。因此虽然最终的程序在表面上仍然与传统意义上的可执行文件都具有“.exe”的后缀名。但是实际上,如果计算机上没有安装.Net Framework,那么这些程序将不能够被执行。在程序执行时,.Net Framework将中间代码翻译成为二进制机器码,从而使它得到正确的运行。最终的二进制代码被存储在一个缓冲区中。所以一旦程序使用了相同的代码,那么将会调用缓冲区中的版本。这样如果一个.Net程序第二次被运行,那么这种翻译不需要进行第二次,速度明显加快。
c#是组件编程,是90年代面向对象编程的深度发展。组件编程已经成为当今世界软件业面向下一代程序开发的一致选择。下面我将给大家介绍c#无处不在的面向组件编程。
首先让我们先暂时告别微软给我们提供的VisualStudio,用windows自带的记事本和微软提供给我们开发者的Visual Studio 2012(2010,2008) 命令提示程序手动运行一个HelloWorld程序。(p.s.如果想用命令提示符[cmd]的话需要配置系统环境变量,就像搭建java环境时做的那样)
(一)新建一个记事本,输入如下代码,另存为HelloWorld.cs,其中".cs"是C#源代码文件的扩展名(注:笔者亲测,源代码用任意的后缀,都可以编译出.exe文件)

- using System;
- namespace HelloWorld
- {
- class Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine("Hello World!");
- Console.ReadKey();
- }
- }
- }

(二)打开Visual Studio 2012(2010,2008) 命令提示程序,如(图一) 注:笔者是win8和vs2012
(图一)
(三)切换到HelloWorld.cs的目录(注:笔者放在D盘),并运行命令:csc /out:Hello.exe HelloWorld.cs 结果如(图二)
(图二)
(四)此时运行Hello.exe将会打印出Hello World.
下面我们来仔细分析上面的代码和整个程序的编译输出及执行过程。
首先第一行的"using System;"语句是C#语言的using命名空间指示符,这里的"System"是微软提供的基础类库。C#语言没有自己的语言类库,它直接获取.NET的系统类库。using语句使得我们可以用简短的别名"Console"来代替类型"System.Console",就像在c++里面的using namespace ***一样,所以同样可以得知using指示符并不是必须的,我们可以用类型的全局名字来获取类型。
接着我们声明并实现了一个含有静态Main()函数的Program类。C#所有的声明和实现都要放在同一个文件里,不像C++那样可以将两者分离。Main()函数在C#里非常特殊,它是编译器规定的所有可执行程序的入口点。由于其特殊性,对Main()函数我们有以下几条准则:
Main()函数必须封装在类或结构里来提供可执行程序的入口点。C#采用了完全的面向对象的编程方式,C#中不可以有像C++那样的全局函数。
Main()函数必须为静态函数(static)。这允许C#不必创建实例对象即可运行程序。
Main()函数保护级别没有特殊要求, public,protected,private等都可,但一般我们都指定其为public。
Main()函数名的第一个字母要大写,否则将不具有入口点的语义。C#是大小写敏感的语言。
Main()函数的参数只有两种参数形式:无参数和string 数组表示的命令行参数,即static void Main()或static void Main(string[]args) ,后者接受命令行参数。一个C#程序中只能有一个Main()函数入口点。其他形式的参数不具有入口点语义,C#不推荐通过其他参数形式重载Main()函数,这会引起编译警告。
Main()函数返回值只能为void(无类型)或int(整数类型)。其他形式的返回值不具有入口点语义。
我们再来看"HelloWorld.cs"程序中Main()函数的内部实现。前面提过,Console是在命名空间System下的一个类,它表示我们通常打交道的控制台。而我们这里是调用其静态方法WriteLine()。如同C++一样,静态方法允许我们直接作用于类而非实例对象。WriteLine()函数接受字符串类型的参数"Hello World !",并把它送入控制台显示。如前所述,C#没有自己的语言类库,它直接获取Microsoft.NET系统类库。我们这里正是通过获取Microsoft.NET系统类库中的System.Console.WriteLine()来完成我们想要的控制台输出操作。这样我们便完成了"Hello World!"程序。
我们先来看我们对"HelloWorld.cs"文件用csc.exe命令编译后发生了什么。首先我们得到了Hello.exe文件。但那仅仅是事情的表象,正如文章开始所说,Hello.exe根本不是一个可执行文件!它是编译输出的HelloWorld.exe是一个由中间语言(IL),元数据(Metadata)和一个额外的被编译器添加的目标平台的标准可执行文件头(比如Win32平台就是加了一个标准Win32可执行文件头)组成的PE(portable executable,可移植执行体)文件,而不是传统的二进制可执行文件--虽然他们有着相同的扩展名。
上面说的过程大致如下图:
中间语言是一组独立于CPU的指令集,它可以被即时编译器翻译成目标平台的本地代码。中间语言代码使得所有Microsoft.NET平台的高级语言C#,VB.NET,VC.NET等得以平台独立,以及语言之间实现互操作。元数据是一个内嵌于PE文件的表的集合。元数据描述了代码中的数据类型等一些通用语言运行时(Common Language Runtime)需要在代码执行时知道的信息。元数据使得.NET应用程序代码具备自描述特性,提供了类型安全保障,这在以前需要额外的类型库或接口定义语言(Interface Definition Language,简称IDL)。
知道了这么多,现在让我们回过头看看刚才我们写的HelloWorld是怎么运行的,首先呗编译器编译生成了程序集,程序集内的是IL代码,根据上面的解释可知它还不是可运行的代码,IL是与CPU无关的机器语言.直到程序集被调用,才会由JIT(Just-in-Time,实时)编译器编译为本机代码(CPU指令).重新强调一下在运行时,CLR执行如下步骤:
- 检查程序集的安全特性;
- 在内存中分配空间;
- 把程序集中的可执行代码发送给JIT编译器,把其中一部分编译成本机代码(CPU指令)就被缓存以备后来的程序中执行.一旦应用程序终止,编译好的本机代码也会被丢弃.例如如果将上面的代码改为:
- static void Main(string[] args)
- {
- Console.WriteLine("Hello");
- Console.WriteLine("World!");
- Console.ReadKey();
- }
第一个WriteLine需要先JIT编译,再执行.而由于已编译WriteLine的代码,所以第二个WriteLine会直接执行内存块中的代码,跳过JIT编译.由于分配内存,JIT编译过程等,所以程序会在第一次运行时造成一些性能损失,写ASP.NET时这种感觉特变明显,按了F5会等很久才会显示首页。
- static void Main(string[] args)
这样的解释可能还是有点让人困惑,那么我们来实际的解剖一下这个PE文件。我们采用的工具是.NET SDK中自带的ildasm.exe,它可以帮助我们提取PE文件中的有关数据。我们键入命令"ildasm /output:HelloWorld.il HelloWorld.exe",一般可以得到两个输出文件:helloworld.il和helloworld.res。其中后者是提取的资源文件,我们暂且不管,我们来看helloworld.il文件。我们用"记事本"程序打开可以看到元数据和中间语言(IL)代码,由于篇幅关系,我们只将其中的中间语言代码提取出来列于下面,有关元数据的表项我们暂且不谈:
- class private auto ansi beforefieldinit Program
- extends [mscorlib]System.Object
- {
- .method public hidebysig static void Main() cil managed
- {
- .entrypoint
- // Code size 11 (0xb)
- .maxstack
- IL_0000: ldstr "Hello World !"
- IL_0005: call void [mscorlib]System.Console::WriteLine(string)
- IL_000a: ret
- } // end of method Program ::Main
- .method public hidebysig specialname rtspecialname
- instance void .ctor() cil managed
- {
- // Code size 7 (0x7)
- .maxstack
- IL_0000: ldarg.
- IL_0001: call instance void [mscorlib]System.Object::.ctor()
- IL_0006: ret
- } // end of method Program::.ctor
- } // end of class Program
我们粗略的感受是它很类似于早先的汇编语言,但它具有了对象定义和操作的功能。我们可以看到它定义并实现了一个继承自System.Object 的Program类及两个函数:Main()和.ctor()。其中.ctor()是Program类的构造函数,可在"HelloWorld.cs"源代码中我们并没有定义构造函数呀--是的,我们没有定义构造函数,但C#的编译器为我们添加了它。你还可以看到C#编译器也强制Program类继承System.Object类,虽然这个我们也没有指定。
下面我们总结一下PE文件时怎样运行的
用户执行编译器输出的应用程序(PE文件),操作系统载入PE文件,以及其他的DLL(.NET动态连接库)。 操作系统装载器根据前面PE文件中的可执行文件头跳转到程序的入口点。显然,操作系统并不能执行中间语言,该入口点也被设计为跳转到mscoree.dll(.NET平台的核心支持DLL)的_ CorExeMain()函数入口。 CorExeMain()函数开始执行PE文件中的中间语言代码。这里的执行的意思是通用语言运行时按照调用的对象方法为单位,用即时编译器将中间语言编译成本地机二进制代码,执行并根据需要存于机器缓存。程序的执行过程中,垃圾收集器负责内存的分配,释放等管理功能。 程序执行完毕,操作系统卸载应用程序。清楚的知晓编译输出的PE文件的执行过程是深度掌握C#语言编程的关键,这种过程的本身就诠释着C#语言的高级内核机制以及其背后Microsoft.NET平台种种诡秘的性质。一个"Hello World !"程序的概括力已经足够,在我们对C#语言有了一个很好的起点之后,下面的专题会和大家一起学习些C#基础特点,看看Microsoft.NET平台构造。
从编译,执行过程理解c#的更多相关文章
- Java编译执行过程
在刷软件设计师中级考试的题目,判断关于编译系统对某高级语言进行翻译的叙述的对错.记得刚开始学Java的时候自己就觉得自己对程序的执行过程理解的相当的透彻,但是一对答案,我的小心脏就有点受不了了,特此在 ...
- C#编译执行过程
前言 大家好,我是卫斯理(Wesley).喜欢武侠的朋友可能知道小说中也有个卫斯理,他是位冒险家,财力充沛,极富冒险精神,并且有着超强的好奇心,对奇异的事情总有"打破沙锅问到底"的 ...
- C程序编译执行过程
C程序编译执行过程 认识C编译执行过程,是C学习的开端. 简单说C语言从编码编译到执行要经历一下过程: C源代码 编译---->形成目标代码,目标代码是在目标机器上运行的代码. 连接-- ...
- go 编译:交叉编译&编译执行过程
1. 交叉编译 编译Windows程序和mac程序 GOOS=windows GOARCH-amd64 go build main.go 转自:https://www.cnblogs.com/mafe ...
- c语言编译执行过程
<h4>认识C编译执行过程</h4>认识C编译执行过程,是C学习的开端.简单说C语言从编码编译到执行要经历一下过程: C源代码编译---->形成目标代码,目标代码是在目标 ...
- C#在.NET编译执行过程
1..NET语言的编译器接受源代码文件,并生成名为程序集的输出文件. 程序集要么是可执行的,要么是DLL 程序集里的代码并不是本机代码,而是一种名称为CIL的中间语言 程序集包含如下信息: 程序的CI ...
- angualar入门学习-- 自定义指令 指令编译执行过程
3个阶段: 一.加载阶段 加载angular.js的源码,找到ng-app确定应用边界范围. 二.编译阶段 compile 查找所有指令,保存在一个列表中 对所有指令按优先级(property属性值) ...
- java代码的编译执行过程
- C C++ Java C# JS编译、执行过程的原理入门分析
C.C++是典型的编译型编程语言,编译链接后,点击则可执行. JS,解释型脚本语言,则不需要进行编译,直接解释执行. Java和C#则是所谓的高级语言,编译执行的方式做了很多处理, 尤其是C#,VS编 ...
随机推荐
- 面向对象的CSS
原文 简书原文:https://www.jianshu.com/p/cb5e9f56ddcc 大纲 1.面向对象的CSS(OOCSS)概念 2.面向对象的CSS的作用 3.面向对象的CSS的注意事项 ...
- 2、Python基本数据类型
1.算数运算: 2.比较运算: 3.赋值运算: 4.逻辑运算: 5.成员运算: 基本数据类型 1.数字 int(整型) 在32位机器上,整数的位数为32位,取值范围为-2**31-2**31-1,即- ...
- OC学习篇之---Foundation框架中的NSArray类和NSMutableArray类
我们继续来看一下Foundation框架中的NSArray类和NSMutableArray类,其实NSArray类和Java中的List差不多,算是一种数据结构,当然我们从这两个类可以看到,NSArr ...
- 怎样cp文件夹时忽略指定的文件夹和文件
在备份ltedecoder程序时,须要把此文件夹拷由到bak文件夹下.但decoder文件夹下有个大文件,不须要备份,还有日志问题,也不须要备份,怎样实现呢?? 方法: cd /source-dir ...
- IIS7配置PHP图解
IIS6整合PHP详解:http://zhidao.zgsj.com/article/8/2011118142648.shtml PHP5.2.17 官方下载: http://windows.php. ...
- Bootstrap相关优质项目学习清单
1:编码规范 by @mdo编写灵活.稳定.高质量的 HTML 和 CSS 代码的规范 http://codeguide.bootcss.com/ 2:快速.可靠.安全的依赖管理工具.Yarn 缓存了 ...
- JAVA后端实现统一扫码支付:微信篇
最近做完了一个项目,正好没事做,产品经理就给我安排了一个任务. 做一个像收钱吧这样可以统一扫码收钱的功能. 一开始并不知道是怎么实现的,咨询了好几个朋友,才知道大概的业务流程:先是开一个网页用 ...
- php面试题四
php面试题四 一.总结 二.php面试题四 01. 输出为 Mozilla/4.0(compatible;MSIE5.01;Window NT 5.0)时,可能的输出语句是: A.$_S ...
- 【record】9.24..10.1
因为参加比赛所以做得比较少了
- [D3] Convert Input Data to Output Values with Linear Scales in D3
Mapping abstract values to visual representations is what data visualization is all about, and that’ ...