41
编编编编编编编编 编编编编编编编编 [email protected] [email protected]

编译原理上机实习

  • Upload
    ivria

  • View
    76

  • Download
    0

Embed Size (px)

DESCRIPTION

编译原理上机实习. [email protected]. 实习题目. 利用词法分析程序的自动构造工具 LEX 构造一个识别 C 语言的 单词的词法分析程序; 利用语法分析程序的自动构造工具 Yacc 构造一个识别 C 语言的 程序的语法分析程序. LEX 简介 YACC 简介 实习要求. LEX 简介. Lex 是一种生成扫描器的工具。扫描器是一种识别文本中的词汇模式的程序。这些词汇模式(或者常规表达式)在一种特殊的句子结构中定义. - PowerPoint PPT Presentation

Citation preview

Page 1: 编译原理上机实习

编译原理上机实习编译原理上机实习

[email protected]@pku.edu.cn

Page 2: 编译原理上机实习

实习题目实习题目

1.1. 利用词法分析程序的自动构造工具利用词法分析程序的自动构造工具 LEXLEX 构构造一个识别造一个识别 CC 语言的单词的词法分析程序;语言的单词的词法分析程序;

2.2. 利用语法分析程序的自动构造工具利用语法分析程序的自动构造工具 YaccYacc构造一个识别构造一个识别 CC 语言的程序的语法分析程语言的程序的语法分析程序序

Page 3: 编译原理上机实习

• LEXLEX 简介简介• YACCYACC 简介简介•实习要求实习要求

Page 4: 编译原理上机实习

LEXLEX 简介简介

• Lex Lex  是一种生成扫描器的工具。扫描器是是一种生成扫描器的工具。扫描器是一种识别文本中的词汇模式的程序。这些一种识别文本中的词汇模式的程序。这些词汇模式(或者常规表达式)在一种特殊词汇模式(或者常规表达式)在一种特殊的句子结构中定义 的句子结构中定义 

Page 5: 编译原理上机实习

• 一种匹配的常规表达式可能会包含相关的动作。一种匹配的常规表达式可能会包含相关的动作。这一动作可能还包括返回一个标记。当 这一动作可能还包括返回一个标记。当  Lex Lex  接收接收到文件或文本形式的输入时,它试图将文本与常到文件或文本形式的输入时,它试图将文本与常规表达式进行匹配。它一次读入一个输入字符,规表达式进行匹配。它一次读入一个输入字符,直到找到一个匹配的模式。如果能够找到一个匹直到找到一个匹配的模式。如果能够找到一个匹配的模式,配的模式, Lex Lex  就执行相关的动作(可能包括返就执行相关的动作(可能包括返回一个标记)。另一方面,如果没有可以匹配的回一个标记)。另一方面,如果没有可以匹配的常规表达式,将会停止进一步的处理,常规表达式,将会停止进一步的处理, Lex Lex  将显将显示一个错误消息。 示一个错误消息。 

Page 6: 编译原理上机实习

Lex Lex  的常规表达式 的常规表达式 

•常规表达式是一种使用元语言的模式描述。常规表达式是一种使用元语言的模式描述。表达式由符号组成。符号一般是字符和数表达式由符号组成。符号一般是字符和数字,但是 字,但是  Lex Lex  中还有一些具有特殊含义的中还有一些具有特殊含义的其他标记。下面两个表格定义了 其他标记。下面两个表格定义了  Lex Lex  中使中使用的一些标记并给出了几个典型的例子。 用的一些标记并给出了几个典型的例子。 

Page 7: 编译原理上机实习

• A-Z, 0-9, a-z A-Z, 0-9, a-z    构成了部分模式的字符和数字。  构成了部分模式的字符和数字。  . .    匹配任意字符,除了 匹配任意字符,除了  \n\n 。  。  - -    用来指定范围。例如:用来指定范围。例如: A-Z A-Z  指从 指从  A A  到 到  Z Z  之间的所有字之间的所有字符。  符。  [ ] [ ]    一个字符集合。匹配括号内的任意 字符。如果第一个字一个字符集合。匹配括号内的任意 字符。如果第一个字符是 符是  ^ ^  那么它表示否定模式。例如那么它表示否定模式。例如 : [abC] : [abC]  匹配 匹配  a, b, a, b,  和和   CC 中的任何一个。  中的任何一个。  

Page 8: 编译原理上机实习

• * *    匹配匹配 00 个或者多个上述的模式。  个或者多个上述的模式。  + +    匹配匹配 11 个或者多个上述模式。  个或者多个上述模式。  ? ?    匹配匹配 00 个或个或 11 个上述模式。  个上述模式。  $ $    作为模式的最后一个字符匹配一行的结尾。  作为模式的最后一个字符匹配一行的结尾。  { } { }    指出一个模式可能出现的次数。 例如指出一个模式可能出现的次数。 例如 : A{1,3} : A{1,3}  表示 表示  A A  可可能出现能出现 11 次或次或 33 次。  次。  \ \    用来转义元字符。同样用来覆盖字符在此表中定义的特用来转义元字符。同样用来覆盖字符在此表中定义的特殊意义,只取字符的本意。  殊意义,只取字符的本意。  ^ ^    否定。  否定。  

Page 9: 编译原理上机实习

• | |    表达式间的逻辑或。  表达式间的逻辑或。  "<"< 一些符号一些符号 >;" >;"    字符的字面含义。元字符具有。  字符的字面含义。元字符具有。  / /    向前匹配。如果在匹配的模版中的“向前匹配。如果在匹配的模版中的“ /”/”后跟有后续表达式,只匹配模版中“后跟有后续表达式,只匹配模版中“ /”/” 前前面的部分。如:如果输入 面的部分。如:如果输入  A01A01 ,那么在模,那么在模版 版  A0/1 A0/1  中的 中的  A0 A0  是匹配的。  是匹配的。  ( ) ( )    将一系列常规表达式分组。 将一系列常规表达式分组。 

Page 10: 编译原理上机实习

常规表达式举例 常规表达式举例 

• joke[rs] joke[rs]    匹配 匹配  jokes jokes  或 或  jokerjoker 。  。  A{1,2}shis+ A{1,2}shis+    匹配 匹配  AAshis, Ashis, AAshi, AshiAAshis, Ashis, AAshi, Ashi 。  。  (A[b-e])+ (A[b-e])+    匹配在 匹配在  A A  出现位置后跟随的从 出现位置后跟随的从  b b  到 到  e e 的所有字符中的 的所有字符中的  0 0  个或 个或  11 个。  个。  

Page 11: 编译原理上机实习

• Lex Lex  中的标记声明类似 中的标记声明类似  C C  中的变量名。每中的变量名。每个标记都有一个相关的表达式。(下表中个标记都有一个相关的表达式。(下表中给出了标记和表达式的例子。)使用这个给出了标记和表达式的例子。)使用这个表中的例子,我们就可以编一个字数统计表中的例子,我们就可以编一个字数统计的程序了。我们的第一个任务就是说明如的程序了。我们的第一个任务就是说明如何声明标记。 何声明标记。 

Page 12: 编译原理上机实习

标记声明举例 标记声明举例 

•标记 相关表达式 含义  标记 相关表达式 含义  数字数字 (number) ([0-9])+ 1(number) ([0-9])+ 1 个或多个数字  个或多个数字  字符字符 (chars) [A-Za-z] (chars) [A-Za-z]  任意字符  任意字符  空格空格 (blank) " " (blank) " "  一个空格  一个空格  字字 (word) (chars)+ 1(word) (chars)+ 1 个或多个 个或多个  chars  chars  变量变量 (variable) ((variable) ( 字符字符 )+()+( 数字数字 )*()*( 字符字符 )*)*(( 数字数字 )*  )*  

Page 13: 编译原理上机实习

Lex Lex  编程 编程 

• Lex Lex  编程可以分为三步: 编程可以分为三步: 

以 以  Lex Lex  可以理解的格式指定模式相关的动可以理解的格式指定模式相关的动作。  作。  在这一文件上运行 在这一文件上运行  LexLex ,生成扫描器的 ,生成扫描器的  C C 代码。  代码。  编译和链接 编译和链接  C C  代码,生成可执行的扫描器。代码,生成可执行的扫描器。    

Page 14: 编译原理上机实习

•一个 一个  Lex Lex  程序分为三个段:第一段是 程序分为三个段:第一段是  C C 和 和  Lex Lex  的全局声明,第二段包括模式(的全局声明,第二段包括模式( C C 代码),第三段是补充的 代码),第三段是补充的  C C  函数。 例如函数。 例如 , , 第三段中一般都有 第三段中一般都有  main() main()  函数。这些段函数。这些段以以 %%%% 来分界。 来分界。 

Page 15: 编译原理上机实习

例:字数统计的 例:字数统计的  Lex Lex  程序 程序 

• C C  和 和  Lex Lex  的全局声明 的全局声明 这一段中我们可以增加 这一段中我们可以增加  C C  变量声明。这里变量声明。这里我们将为字数统计程序声明一个整型变量,我们将为字数统计程序声明一个整型变量,来保存程序统计出来的字数。我们还将进来保存程序统计出来的字数。我们还将进行 行  Lex Lex  的标记声明。 的标记声明。 

Page 16: 编译原理上机实习

字数统计程序的声明 字数统计程序的声明 

• %{ %{         int wordCount = 0;         int wordCount = 0;         %}         %}         chars [A-za-z\        chars [A-za-z\ 抃抃 '\.\"] '\.\"]         numbers ([0-9])+         numbers ([0-9])+         delim [" "\n\t]         delim [" "\n\t]         whitespace {delim}+         whitespace {delim}+         words {chars}+         words {chars}+         %%         %% 两个百分号标记指出了 两个百分号标记指出了  Lex Lex  程序中这一段的结束和三段程序中这一段的结束和三段中第二段的开始。 中第二段的开始。 

Page 17: 编译原理上机实习

Lex Lex  的模式匹配规则 的模式匹配规则 

• 字数统计程序中的 字数统计程序中的  Lex Lex  规则 规则 • {words} { wordCount++; /* {words} { wordCount++; /*         increase the word count by one*/ }         increase the word count by one*/ } 

        {whitespace} { /* do         {whitespace} { /* do         nothing*/ }         nothing*/ } 

        {numbers} { /* one may         {numbers} { /* one may         want to add some processing here*/ }         want to add some processing here*/ } 

        %%         %% 

Page 18: 编译原理上机实习

C C 代码 代码 

• Lex Lex  编程的第三段,也就是最后一段覆盖编程的第三段,也就是最后一段覆盖了 了  C C  的函数声明(有时是主函数)。注意的函数声明(有时是主函数)。注意这一段必须包括 这一段必须包括  yywrap() yywrap()  函数。 函数。  Lex Lex  有有一套可供使用的函数和变量。 其中之一就一套可供使用的函数和变量。 其中之一就是 是  yywrapyywrap 。一般来说,。一般来说, yywrap() yywrap()  的定义的定义如下例。 如下例。 

Page 19: 编译原理上机实习

字数统计程序的 字数统计程序的  C C 代码段 代码段 • void main() void main() 

        {         { 

        yylex(); /* start the         yylex(); /* start the         analysis*/         analysis*/ 

        printf(" No of words:         printf(" No of words:         %d\n", wordCount);         %d\n", wordCount); 

        }         } 

        int yywrap()         int yywrap() 

        {         { 

        return 1;         return 1; 

        }         } 

Page 20: 编译原理上机实习

高级 高级  Lex Lex 

• Lex Lex  变量 变量 

yyin FILE* yyin FILE*  类型。 它指向 类型。 它指向  lexer lexer 正在解析的当前文件。  正在解析的当前文件。  yyout FILE* yyout FILE*  类型。 它指向记录 类型。 它指向记录  lexer lexer  输出的位置。 缺输出的位置。 缺省情况下,省情况下, yyin yyin  和 和  yyout yyout 都指向标准输入和输出。  都指向标准输入和输出。  yytext yytext  匹配模式的文本存储在这一变量中(匹配模式的文本存储在这一变量中( char*char* )。  )。  yyleng yyleng  给出匹配模式的长度。  给出匹配模式的长度。  yylineno yylineno 提供当前的行数信息。(提供当前的行数信息。( lexerlexer 不一定支持。)不一定支持。)    

Page 21: 编译原理上机实习

高级 高级  Lex ---Lex Lex ---Lex  函数 函数 

• yylex() yylex()  这一函数开始分析。 它由 这一函数开始分析。 它由  Lex Lex  自动生成。  自动生成。  yywrap() yywrap()  这一函数在文件(或输入)的末尾调用。如果这一函数在文件(或输入)的末尾调用。如果函数的返回值是函数的返回值是 11 ,就停止解析。 因此它可以用来解析多,就停止解析。 因此它可以用来解析多个文件。代码可以写在第三段,这就能够解析多个文件。 个文件。代码可以写在第三段,这就能够解析多个文件。 方法是使用 方法是使用  yyin yyin  文件指针(见上表)指向不同的文件,文件指针(见上表)指向不同的文件,直到所有的文件都被解析。最后,直到所有的文件都被解析。最后, yywrap() yywrap()  可以返回 可以返回  1 1 来表示解析的结束。  来表示解析的结束。  yyless(int n) yyless(int n)  这一函数可以用来送回除了前憂这一函数可以用来送回除了前憂 ? ?  个字符外个字符外的所有读出标记。  的所有读出标记。  yymore() yymore()  这一函数告诉 这一函数告诉  Lexer Lexer  将下一个标记附加到当前将下一个标记附加到当前标记后。  标记后。  

Page 22: 编译原理上机实习

YACCYACC 语言简介语言简介

•什么是语法 :什么是语法 :•我们看到 我们看到  Lex Lex  从输入序列中识别标记。如从输入序列中识别标记。如果你在查看标记序列,你可能想在这一序果你在查看标记序列,你可能想在这一序列出现时执行某一动作。这种情况下有效列出现时执行某一动作。这种情况下有效序列的规范称为语法。序列的规范称为语法。 Yacc Yacc  语法文件包括语法文件包括这一语法规范。它还包含了序列匹配时你这一语法规范。它还包含了序列匹配时你想要做的事。 想要做的事。 

Page 23: 编译原理上机实习

• 以英语为例, 这一套标记可能是:名词以英语为例, 这一套标记可能是:名词 , ,  动词动词 , , 形容词等等。为了使用这些标记造一个语法正确形容词等等。为了使用这些标记造一个语法正确的句子,你的结构必须符合一定的规则。一个简的句子,你的结构必须符合一定的规则。一个简单的句子可能是名词单的句子可能是名词 ++ 动词或者名词动词或者名词 ++ 动词动词 ++名名词。词。

• 所以在我们这里,标记本身来自语言(所以在我们这里,标记本身来自语言( LexLex ),),并且标记序列允许用 并且标记序列允许用  Yacc Yacc  来指定这些标记来指定这些标记 (( 标标记序列也叫语法记序列也叫语法 )) 。 。   

Page 24: 编译原理上机实习

终端和非终端符号 终端和非终端符号 

• 终端符号终端符号 : : 代表一类在语法结构上等效的标记。 代表一类在语法结构上等效的标记。 • 终端符号有三种类型: 终端符号有三种类型: 

命名标记命名标记 : :  这些由 这些由  %token %token  标识符来定义。按照惯例,它们都是大标识符来定义。按照惯例,它们都是大写。 写。 

字符标记字符标记 : :  字符常量的写法与 字符常量的写法与  C C  相同。例如相同。例如 , ?? , ??  就是一个字符标记。就是一个字符标记。  

字符串标记 字符串标记  : : 写法与 写法与  C C  的字符串常量相同。例如,的字符串常量相同。例如, "<<" "<<"  就是一个就是一个字符串标记。 字符串标记。 

lexer lexer  返回命名标记。 返回命名标记。 

Page 25: 编译原理上机实习

•非终端符号非终端符号 : :  是一组非终端符号和终端符是一组非终端符号和终端符号组成的符号。按照惯例,它们都是小写。号组成的符号。按照惯例,它们都是小写。 在例子中, 在例子中, file file  是一个非终端标记而 是一个非终端标记而  NANAME ME  是一个终端标记。 是一个终端标记。 

Page 26: 编译原理上机实习

用 用  Yacc Yacc  来创建一个编译器包括四来创建一个编译器包括四个步骤: 个步骤: •编写一个 编写一个  .y .y  的语法文件(同时说明 的语法文件(同时说明  C C  在在这里要进行的动作)。  这里要进行的动作)。  编写一个词法分析器来处理输入并将标记传编写一个词法分析器来处理输入并将标记传递给解析器。 这可以使用 递给解析器。 这可以使用  Lex Lex  来完成。  来完成。  编写一个函数,通过调用 编写一个函数,通过调用  yyparse() yyparse()  来开始来开始解析。  解析。  编写错误处理例程(如 编写错误处理例程(如  yyerror()yyerror() )。  )。  编译 编译  Yacc Yacc  生成的代码以及其他相关的源文生成的代码以及其他相关的源文件。 件。 

Page 27: 编译原理上机实习

用 用  Yacc Yacc  编写语法 编写语法 

•如同 如同  Lex Lex  一样一样 , ,  一个 一个  Yacc Yacc  程序也用双百程序也用双百分号分为三段。它们是:声明、语法规则分号分为三段。它们是:声明、语法规则和 和  C C  代码。 我们将解析一个格式为 姓名 代码。 我们将解析一个格式为 姓名 = =  年龄的文件作为例子,来说明语法规则。年龄的文件作为例子,来说明语法规则。我们假设文件有多个姓名和年龄,它们以我们假设文件有多个姓名和年龄,它们以空格分隔。 在看 空格分隔。 在看  Yacc Yacc  程序的每一段时,程序的每一段时,我们将为我们的例子编写一个语法文件。 我们将为我们的例子编写一个语法文件。 

Page 28: 编译原理上机实习

C C  与 与  Yacc Yacc  的声明 的声明 

• C C  声明可能会定义动作中使用的类型和变声明可能会定义动作中使用的类型和变量,以及宏。还可以包含头文件。每个 量,以及宏。还可以包含头文件。每个  YaYacc cc  声明段声明了终端符号和非终端符号声明段声明了终端符号和非终端符号(标记)的名称,还可能描述操作符优先(标记)的名称,还可能描述操作符优先级和针对不同符号的数据类型。 级和针对不同符号的数据类型。  lexer (Lelexer (Lex) x)  一般返回这些标记。所有这些标记都必一般返回这些标记。所有这些标记都必须在 须在  Yacc Yacc  声明中进行说明。 声明中进行说明。 

Page 29: 编译原理上机实习

• 在文件解析的例子中我们感兴趣的是这些标记:在文件解析的例子中我们感兴趣的是这些标记: name, equal sname, equal sign, ign,  和 和  ageage 。。 Name Name  是一个完全由字符组成的值。 是一个完全由字符组成的值。  Age Age  是数是数字。于是声明段就会像这样: 字。于是声明段就会像这样: 

文件解析例子的声明 文件解析例子的声明 

               % % 

        #typedef char* string; /*         #typedef char* string; /*         to specify token types as char* */         to specify token types as char* */ 

        #define YYSTYPE string /*         #define YYSTYPE string /*         a Yacc variable which has the value of returned token */         a Yacc variable which has the value of returned token */ 

        %}         %} 

        %token NAME EQ AGE         %token NAME EQ AGE 

        %%         %% 

Page 30: 编译原理上机实习

•类似 类似  Lex, Yacc Lex, Yacc  也有一套变量和函数可供也有一套变量和函数可供用户来进行功能扩展。 用户来进行功能扩展。  YYSTYPE YYSTYPE  定义了定义了用来将值从 用来将值从  lexer lexer  拷贝到解析器或者 拷贝到解析器或者  YaccYacc   的 的  yylval yylval  (另一个 (另一个  Yacc Yacc  变量)的类型。变量)的类型。默认的类型是 默认的类型是  intint 。 由于字符串可以从 。 由于字符串可以从  lelexer xer  拷贝,类型被重定义为 拷贝,类型被重定义为  char*char* 。 。 

Page 31: 编译原理上机实习

Yacc Yacc  语法规则 语法规则 

• Yacc Yacc  语法规则具有以下一般格式: 语法规则具有以下一般格式: 

               result: components { /* result: components { /*         action to be taken in C */ }         action to be taken in C */ } 

        ;         ; 在这个例子中,在这个例子中, result result  是规则描述的非终端符号。是规则描述的非终端符号。Components Components  是根据规则放在一起的不同的终端是根据规则放在一起的不同的终端和非终端符号。 如果匹配特定序列的话 和非终端符号。 如果匹配特定序列的话  ComponComponents ents  后面可以跟随要执行的动作。 后面可以跟随要执行的动作。 

Page 32: 编译原理上机实习

• param : NAME EQ NAME { param : NAME EQ NAME {         printf("\tName:%s\tValue(name):%s\n", $        printf("\tName:%s\tValue(name):%s\n", $1,$3);} 1,$3);} 

            | NAME EQ VALUE{             | NAME EQ VALUE{             printf("\tName:%s\tValue(value):%s\n",            printf("\tName:%s\tValue(value):%s\n",$1,$3);} $1,$3);} 

        ;         ; 

Page 33: 编译原理上机实习

•如果上例中序列 如果上例中序列  NAME EQ NAME NAME EQ NAME  被匹配,被匹配,将执行相应的 将执行相应的  { } { }  括号中的动作。 这里另括号中的动作。 这里另一个有用的就是 一个有用的就是  $1 $1  和 和  $3 $3  的使用的使用 , ,  它们它们引用了标记 引用了标记  NAME NAME  和 和  NAMENAME (或者第二(或者第二行的 行的  VALUEVALUE )的值。 )的值。 

Page 34: 编译原理上机实习

• 标记 标记  NAME NAME  的 的  Lex Lex 代码是这样的 代码是这样的 • char [A-Za-z] char [A-Za-z] 

        name {char}+         name {char}+ 

        %%         %% 

        {name} { yylval = strdup(yytext);         {name} { yylval = strdup(yytext);         return NAME; }         return NAME; } 

Page 35: 编译原理上机实习

• 文件解析例子的规则段是这样的: 文件解析例子的规则段是这样的: • 文件解析的语法 文件解析的语法 

               file : record file file : record file 

        | record         | record 

        ;         ; 

        record: NAME EQ AGE {         record: NAME EQ AGE {         printf("%s is now %s years old!!!", $1, $3);}         printf("%s is now %s years old!!!", $1, $3);} 

        ;         ; 

        %%         %% 

Page 36: 编译原理上机实习

附加 附加  C C 代码 代码 

•一个函数如 一个函数如  main() main()  调用 调用  yyparse() yyparse()  函数函数(( Yacc Yacc  中 中  Lex Lex  的 的  yylex() yylex()  等效函数)。 等效函数)。 一般来说,一般来说, Yacc Yacc  最好提供 最好提供  yyerror(char yyerror(char msg) msg)  函数的代码。 当解析器遇到错误时函数的代码。 当解析器遇到错误时调用 调用  yyerror(char msg)yyerror(char msg) 。错误消息作为。错误消息作为参数来传递。 参数来传递。 

Page 37: 编译原理上机实习

• 一个简单的 一个简单的  yyerror( char* ) yyerror( char* )  可能是这样的: 可能是这样的: 

               int yyerror(char* msg) int yyerror(char* msg) 

        {         { 

        printf("Error: %s         printf("Error: %s         encountered at line number:%d\n", msg, yylinen        encountered at line number:%d\n", msg, yylineno); o); 

        }         } 

Page 38: 编译原理上机实习

• 这一段还包括文件解析例子的主函数: 这一段还包括文件解析例子的主函数: 

附加 附加  C C 代码 代码 

               void main() void main() 

        {         { 

            yyparse();             yyparse(); 

        }         } 

        int yyerror(char* msg)         int yyerror(char* msg) 

        {         { 

        printf("Error: %s         printf("Error: %s         encountered \n", msg);         encountered \n", msg); 

Page 39: 编译原理上机实习

实习要求实习要求

•用用 LEXLEX 编写编写 CC 语言词法分析器语言词法分析器•输入:输入: CC 语言程序   输出:词法分析结果语言程序   输出:词法分析结果•用用 YACCYACC 编写编写 CC 语言语法分析器语言语法分析器•提交:提交: .c.c 文件和文件和 .exe.exe 文件文件•提交时间:最晚考试前提交提交时间:最晚考试前提交• ftpftp 上有上有 PascalPascal的例子的例子

Page 40: 编译原理上机实习

需要的软件需要的软件

• Parser GeneratorParser Generator•在在 ftpftp 上包括这个软件的使用方法和安装文上包括这个软件的使用方法和安装文件件

• VC6VC6或或 77

Page 41: 编译原理上机实习

CC 语言语言

    auto break case char const continueauto break case char const continue    ddefaulefault t dodo doubledouble elseelse enumenum externextern

floatfloat forfor gotogoto ifif intint longlong registerregister returnreturn shortshort signedsigned sizeofsizeof staticstatic structstruct switchswitch typedeftypedef unionunion unsignedunsigned voidvoid volatilevolatile whilewhile