C#专题之C#的编译、文法及预处理

来源:互联网 发布:登录facebook网络错误 编辑:程序博客网 时间:2024/06/06 15:51
 作者:思多雅[天行健] 2008-09-25发布
在本专题中,我们将一起对C#的编译、方法及预处理进行探讨。
一、C#的编译
    在谈及C#的编译之前,我们了解一点:计算机不能直接理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序。翻译的方式有两种,一个是编译,一个是解释。
    两种方式只是翻译的时间不同。编译型语言写的程序执行之前,需要一个专门的编译过程,把程序编译成为机器语言的文件,比如exe(com)文件,以后要运行的话就不用重新翻译了,直接使用编译的结果就行了(exe文件)。
C#的编译阶段如下:
    C#的编译程序把一个C#源程序翻译成目标程序的工作过程分为五个阶段:1、词法分析;2、语法分析;3、中间代码生成;4、代码优化;5、目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。 
  1、词法分析 
  词法分析的任务是对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。执行词法分析的程序称为词法分析程序或扫描器。 
  源程序中的单词符号经扫描器分析,一般产生二元式:单词种别;单词自身的值。单词种别通常用整数编码,如果一个种别只含一个单词符号,那么对这个单词符号,种别编码就完全代表它自身的值了。若一个种别含有许多个单词符号,那么,对于它的每个单词符号,除了给出种别编码以外,还应给出自身的值。 
  词法分析器一般来说有两种方法构造:手工构造和自动生成。手工构造可使用状态图进行工作,自动生成使用确定的有限自动机来实现。 
  2、语法分析 
  编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,如表达式、赋值、循环等,最后看是否构成一个符合要求的程序,按该语言使用的语法规则分析检查每条语句是否有正确的逻辑结构,程序是最终的一个语法单位。编译程序的语法规则可用上下文无关文法来刻画。 
  语法分析的方法分为两种:自上而下分析法和自下而上分析法。自上而下就是从文法的开始符号出发,向下推导,推出句子。而自下而上分析法采用的是移进归约法,基本思想是:用一个寄存符号的先进后出栈,把输入符号一个一个地移进栈里,当栈顶形成某个产生式的一个候选式时,即把栈顶的这一部分归约成该产生式的左邻符号。 
  3、中间代码生成 
  中间代码是源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现。中间代码即为中间语言程序,中间语言的复杂性介于源程序语言和机器语言之间。中间语言有多种形式,常见的有逆波兰记号、四元式、三元式和树。 
  4、代码优化 
  代码优化是指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。所谓等价,是指不改变程序的运行结果。所谓有效,主要指目标代码运行时间较短,以及占用的存储空间较小。这种变换称为优化。 
  有两类优化:一类是对语法分析后的中间代码进行优化,它不依赖于具体的计算机;另一类是在生成目标代码时进行的,它在很大程度上依赖于具体的计算机。对于前一类优化,根据它所涉及的程序范围可分为局部优化、循环优化和全局优化三个不同的级别。 
  5、目标代码生成 
  目标代码生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。 
  目标代码生成阶段应考虑直接影响到目标代码速度的三个问题:一是如何生成较短的目标代码;二是如何充分利用计算机中的寄存器,减少目标代码访问存储单元的次数;三是如何充分利用计算机指令系统的特点,以提高目标代码的质量。
  6、表格管理程序
  编译过程中源程序的各种信息被保留在种种不同的表格,编译各阶段的工作都涉及到构造、查找、或更新有关的表格。
  7、出错处理程序
    如果编译过程中发现源程序有错误,编译程序应报告错误的性质和错误的发生的地点,并且将错误所造成的影响限制在尽可能小的范围内,使得源程序的其余部分能继续被编译下去。

    这也是一般的编译语言的编译过程,但要注意的是C#有一个特殊的地方,那就是C#可以先编译成TL文件,将代码编译成中间代码(IL)既不是源程序也不是cpu指令,程序运行时JIT将IL翻译成本地cpu指令再执行,由于编译的是中间语言,因此速度比一般的解释性语言要快。
     JAVA也有这个特性,java程序也需要编译,但是没有直接编译称为机器语言,而是编译称为字节码,然后用解释方式执行字节码。
    因此,也有学者把认为这二个语言既不是传统的编译语言,也不是传统解释语言。

-------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明-------

二、C#的文法符号 

    一个C#程序由一个或多个源文件组成。一个源文件是一个统一字符编码的字符的有序序列。源文件通常和文件系统中的文件有一一对应关系,但是这个对应关系并不需要。 
    C#的词汇和句子的文法散布在整个文章中。词汇文法定义如能把字符组合为形式标记;句子的文法定义了如何把标记组合为C#程序。 
    文法生成包括无词尾符号和有词尾符号。在文法生成当中,无词尾符号用意大利体表示,而有词尾符号用定宽字体。每一个无词尾符号定义为一系列产品(production)。这一系列产品的第一行是无词尾符号的名称,接下来是一个冒号。对于一个产品,每个连续的锯齿状的行的右手边同左手边类似是无词尾符号。
例子: 
      nonsense: 
         terminal1 
         terminal2 
    定义了一个名为nonsense 的无词尾符号,有两个产品,一个在右手边是terminal1,一个在左手边是 terminal2。 
    选项通常列为单独的一行,虽然有时有很多选项,短语“one of”会在选项前面。这里有一个对把每个选项都列在单独一行的简单缩写的方法。
例子 
      letter: one of 
         A  B  C  a  b  c 
简写为: 
      letter: one of 
         A 
         B 
         C 
         a 
         b 
         c 
如identifier opt ,一个写在下方的前缀 “opt”用来作为简写来指明一个可选的符号。例子 
      whole: 
         first-part second-partopt last-par t 
是下面的缩写: 
        whole: 
            fir st-part last-part 
            fir st-part second-part   last-part 

-------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明-------

三、C#的预处理 
    预处理阶段是一个文本到文本的转换阶段,在预处理过程中,使能进行代码的条件包含和排除。 
        pp-un it: 
            pp-gro up opt 
        pp-gro up: 
            pp-gro up-part 
            pp-gro up   pp-group-part 
        pp-gro up-part: 
            pp-tokensopt   new-line 
            pp-de claration 
            pp-if -section 
            pp-con trol-line 
            pp-l ine-number 
        pp-tokens: 
            pp-token 
            pp-tokens   pp-token 
        pp-token: 
             identifi er 
             keyword 
             literal 
             operator-or-punctuator 
        new-line: 
             The carriage return character (U+000D) 
             The line feed character (U+000A) 
             The carriage return character followed by a line feed character 
             The line separator character (U+2028) 
             The paragraph separator character (U+2029) 

1、预处理声明 
    在预处理过程中,为了使用名称可以被定义和取消定义。#define 定义一个标识符。#undef  “反定义”一个标识符,如果一个标识符在以前已经被定义了,那么它就变成了不明确的。如果一个标识符已经被定义了,它的语意就等同于true ;如果一个标识符没有意义,那么它的语意等同于false。 
        pp-de claration: 
             #define pp-identifier 
             #undef pp-identifier 
来看看这个例子: 
        #define A 
        #undef B 
         class C 
         { 
      #if A 
          void F()  
       #else 
           void G()  
       #endif 
       #if B 
           void H()  
       #else 
           void I()  
       #endif 
       } 
变为: 
       class C 
       { 
           void F()  
           void I()  
       } 
如果有一个pp-unit, 声明就必须用pp- token 元素进行。换句话说,#define 和#undef 必须在文件中任何 “真正代码”前声明,否则在编译时会发生错误。因此,也许会像下面的例子一样散布#if 和#define: 
       #define A 
       #if A 
            #define B 
       #endif 
       namespace N 
       { 
            #if B 
            class Class1  
            #endif 
       } 
因为#define 放在了真实代码后面,所以下面的例子是非法的: 
       #define A 
       namespace N 
       { 
            #define B 
            #if B 
            class Class1  
            #endif 
       } 
一个#undef 也许会“反定义”一个没有定义的名称。下面的例子中定义了一个名字并且对它进行了两次反定义,第二个#undef 没有效果,但还是合法的。 
       #define A 
       #undef A 
       #undef A 

2、 #if, #elif, #else, #endif 
    pp-if -section 用来对程序文本的一部件进行有条件地包括和排除。 
       pp-if -section: 
           pp-if -group   pp-elif-groupsopt pp-else-groupopt pp-endif-line 
       pp-if -group: 
           #if pp-expression  new-line  pp -group opt 
      pp-e lif -groups 
           pp-e lif -group 
           pp-e lif -groups pp-elif-group 
       pp-e lif -group: 
           #elif pp-expression    new-line  group opt 
       pp-e lse-group: 
           #else new-line   group opt 
       pp-end if -line 
           #endif new-line 
举个例子: 
        #define Debug 
        class Class1 
        { 
        #if Debug 
           void Trace(string s)  
        #endif 
        } 
变成: 
        class Class1 
        { 
           void Trace(string s)  
        } 
如果这部分可以嵌套。
来看看例子: 
        #define Debug         // Debugging on 
        #undef Trace          // Tracing off 
        class PurchaseTransaction 
        { 
            void Commit() { 
               #if Debug 
                   CheckConsistency(); 
                   #if Trace 
                       WriteToLog(this.ToString()); 
                   #endif 
               #endif 
               CommitHelper(); 
            } 
        } 

3、预处理控制行 
    特性#error和#warning使得代码可以把警告和错误的条件报告给编译程序,来查出标准的编译时的警告和错误。 

       pp-con trol-line: 
           #error pp-message 
           #warning pp-message 
       pp-message: 
           pp-tokensopt 
举个例子帮助大家理解 
        #warning Code review needed before check-in 
        #define DEBUG 
        #if DEBUG && RETAIL 
            #error A build can't be both debug and retail! 
        #endif 
        class Class1 
        {…} 
    这将总是产生警告(“Code review needed before check-in"),并且如果予处理修饰符DEBUG 和RETAIL 都被定义,还会产生错误。 

4、 #line 
 #line 的特点使得开发者可以改变行的数量和编译器输出时使用的源文件名称,例如警告和错误。如果没有行指示符,那么行的数量和文件名称就会自动由编译器定义。#line指示符通常用于编程后的工具,它从其它文本输入产生C#源代码。 
        pp-l ine-number: 
            #line integer-literal 
            #line integer-literal  string-literal 
        pp-in teger-literal: 
            decimal-digit 
            decimal-digits  decimal-digit 
        pp-s tring-literal: 
            " pp-string-literal-characters  " 
        pp -string-literal-characters: 
            pp-s tring-literal-character 
            pp-s tring-literal-characters  pp-string-literal-character 
        pp-s tring-literal-character: 
            Any character except " (U+0022), and white-space 

5、预处理标识符 
    预处理标识符使用和规则C#标识符文法相似的文法: 
        pp -identifi er: 
            pp-ava ilable-identifier 
        pp-ava ilable-identifi er: 
            A pp-identif ier-or-keyword that is not true or false 
        pp-id entif ier-or-keyword: 
            identifi er-start-character identif ier-part-characters opt 
true 和false 符号不是合法的预定义指示符,所以不能用于#define 的定义和#undef 的反定义。 

6、预处理表达式 
    操作符!, ==,  !=, && 和||是允许的预定义表达式。在预定义表达式中,圆括号可以用来分组。 
        pp-expression: 
            pp-equality-expression 
       pp-pr imary-expression: 
           true 
           false 
           pp -identifi er 
            ( pp-expression  ) 

       pp-unary-expression: 
           pp-pr imary-expression 
            ! pp-unary-expression 
       pp-equality-expression: 
           pp-equality-expression == pp-logical-and-expression 
           pp-equality-expression != pp-logical-and-expression 
       pp-logical-and-expression: 
           pp-unary-expression 
           pp-logical-and-expression && pp-unary-expression 
       pp-logical-or-expression: 
           pp-logical-and-expression 
           pp-logical-or-expression || pp-logical-and-expression 

7、与空白交互作用 
    条件编译标识符必须在一行的第一个非空白位置。
一个单行注释可以跟在条件编译指示符或pp-c ontrol-line 标识符后面。
例如: 
       #define Debug              // Defined if the build is a debug build 
对于pp-control-line 标识符,一行的剩余组成pp-message,独立于此行的注释。
看看例子: 
       #warning                   // TODO: Add a better warning 
会有一个注释为"// TODO: Add a better warning"的警告。 
一个多行注释的起始和结束可以不在同一行中,就像条件编译标识符。
就像这个例子: 
       /* This comment is illegal because it 
        ends on the same line*/ #define Debug 
       /* This is comment is illegal because it is on the same line */ #define 
        Retail 
       #define A /* This is comment is illegal because it is on the same line */ 
       #define B /* This comment is illegal because it starts 
        on the same line */ 
结果将是编译时错误。 
可以形成一个条件编译标识符的数据符号可能会隐含在注释中。
看看这个例子: 
       // This entire line is a commment.  #define Debug 
       /* This text would be a cc directive but it is commented out: 
               #define Retail 
        */ 
不包含任何条件编译标识符,然而完全由空白组成。 
《未完待续:C#专题之语法和句法分析》

-------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明-------

小知识:编译语言与解释语言的优缺点对比
1、运行效率:
编译语言需要编译一次,运行直接执行、不需要翻译,所以编译型语言的程序执行效率高。而解释语言则不同,解释型语言的程序不需要编译,省了道工序,解释性语言在运行程序的时候才翻译,比如解释型basic语言,专门有一个解释器能够直接执行basic程序,每个语句都是执行的时候才翻译。这样解释性语言每执行一次就要翻译一次,效率比较低。 
   解释执行的语言因为解释器不需要直接同机器码打交道所以实现起来较为简单、而且便于在不同的平台上面移植,这一点从现在的编程语言解释执行的居多就能看出来,如 Visual Basic、Visual Foxpro、Power Builder、Java...等。编译执行的语言因为要直接同CPU 的指令集打交道,具有很强的指令依赖性和系统依赖性,但编译后的程序执行效率要比解释语言要高的多,象现在的 Visual C/C++、Delphi 等都是很好的编译语言。
2、代码安全性    
对于解释语言与编译语言所编制出来的代码安全性上而言,可以说是各有优缺点。曾经在 Windows 下跟踪调式过 VB 程序的朋友一般都知道,程序代码 99% 的时间里都是在 VBRUNxx 里转来转去,根本看不出一个所以然来。 这是因为你跟踪的是 VB 的解释器,要从解释器中看出代码的目的是什么是相当困难的。但解释语言有一个致命的弱点,那就是解释语言的程序代码都是以伪码的方式存放的,一旦被人找到了伪码与源码之间的对应关系,就很容易做出一个反编译器出来,你的源程序等于被公开了一样。而编译语言因为直接把用户程序 编译成机器码,再经过优化程序的优化,很难从程序返回到你的源程序的状态, 但对于熟悉汇编语言的解密者来说,也很容易通过跟踪你的代码来确定某些代码的用途。
看到这里,可别打花花肠子去搞什么破解,这个跟偶可没什么关联的哦。
原创粉丝点击