Loading

编译原理实验二

实验目的

本实验的目的是了解工业界常用的编译器 GCC 和 LLVM,熟悉编译器的安装和使用过程,观察编译器工作过程中生成的中间文件的格式和内容,了解编译器的优化效果,为编译器的学习和构造奠定基础。

实验内容

本实验主要的内容为在 Linux 平台上安装和运行工业界常用的编译器 GCC 和LLVM,如果系统中没有安装,则需要首先安装编译器,安装完成后编写简单的测试程序,使用编译器编译,并观察中间输出结果。

实现的具体过程和步骤

GCC配置以及实验过程

  • 查看编译器的版本
gcc -v
  • 使用编译器编译单个文件 test.c,代码见文末附录
gcc test.c -o test
  • 使用编译器编译链接多个文件
gcc -c test1.c
gcc -c test2.c
gcc test1.o test2.o -lm -o program
  • 查看预处理结果:gcc -E hello.c -o hello.i
  • 查看语法分析树:gcc -fdump-tree-all hello.c
  • 查看中间代码生成结果:Code generation result: gcc -fdump-rtl-all hello.c
  • 查看生成的目标代码(汇编代码):gcc –S hello.c –o hello.s

LLVM配置以及实验过程

安装必要工具

sudo apt-get install build-essential
sudo apt-get install cmake
sudo apt-get install python3.8

安装llvm

sudo wget https://github.com/llvm/llvm-project/archive/llvmorg-10.0.1.tar.gz
sudo tar -zxvf llvmorg-10.0.1.tar.gz

//进入llvm-project 目录下 
cd llvm-project
sudo mkdir build 
cd build
sudo cmake -DCMAKE_BUILD_TYPE=Release --enable-optimized --enable-targets=host-only -DLLVM_ENABLE_PROJECTS=clang -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/opt/llvm-10 ../llvm
sudo make -j 4  
sudo make install

安装clang

sudo apt install clang

查看clang版本

clang --version

安装clang-extra-tools工具
进入到上面的build文件夹

sudo cmake -DCMAKE_BUILD_TYPE=Release --enable-optimized --enable-targets=host-only -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;compiler-rt" -G "Unix Makefiles" ../llvm

然后编译安装

sudo make -j 4  //4表示线程数,可修改
sudo make install

安装clang-tidy

sudo apt install clang-tidy

查看clang-tidy版本

clang-tidy --version

下载libc-dev库文件,它包含了C标准库和其他基本的系统库

sudo apt-get install libc-dev
  • 查看编译器的版本

键入以下命令

clang -v
  • 使用编译器编译单个文件 test.c,代码见文末附录

生成具有调试信息的可执行文件

clang -g test.c -o test
./test
  • 使用编译器编译链接多个文件

编写C程序t1.c和t2.c(代码见文末附录)非递归求解汉诺塔问题,以此来验证O2优化的效果

clang t1.c t2.c -o program0
clang t1.c t2.c -O2 -o program1		//O2优化
./program0
./program1

编写两个.c文件test1.c和test2.c(代码见文末附录),解决汉诺塔问题,两个文件是有关系的
键入以下命令

clang test1.c test2.c -Wl,-rpath,/path/to/lib -L/path/to/lib -lc -o program
./program

这个命令将会编译test1.c和test2.c两个文件,链接libc.so库,并将生成的可执行文件命名为program。其中,-Wl选项用来传递链接器选项,-rpath选项指定共享库的路径,-L选项指定库文件的路径,-l选项指定需要链接的库名。

  • 查看编译流程和阶段:clang -ccc-print-phases test.c -c
  • 查看词法分析结果:clang test.c -Xclang -dump-tokens
  • 查看词法分析结果 2:clang test.c -Xclang -dump-raw-tokens
  • 查看语义分析结果:clang test.c -Xclang -ast-dump
  • 查看语义分析结果 2:clang test.c -Xclang -ast-view
  • 查看编译优化的结果:clang test.c -S -mllvm -print-after-all
  • 查看生成的目标代码结果:Target code generation:clang –S test.c

GCC和LLVM对比实验

编译comp.c程序(具体代码见附录)

gcc comp.c -o comp
gcc comp.c -O0 -o comp0
gcc comp.c -O1 -o comp1
gcc comp.c -O2 -o comp2
gcc comp.c -O3 -o comp3
gcc comp.cpp -o comp
gcc comp.cpp -O0 -o comp0
gcc comp.cpp -O1 -o comp1
gcc comp.cpp -O2 -o comp2
gcc comp.cpp -O3 -o comp3
./comp
./comp0
./comp1
./comp2
./comp3
clang comp.c -o compl
clang comp.c -O0 -o compl0
clang comp.c -O1 -o compl1
clang comp.c -O2 -o compl2
clang comp.c -O3 -o compl3
clang comp.cpp -o compl
clang comp.cpp -O0 -o compl0
clang comp.cpp -O1 -o compl1
clang comp.cpp -O2 -o compl2
clang comp.cpp -O3 -o compl3
./compl
./compl0
./compl1
./compl2
./compl3

GCC 运行结果分析

  • 查看编译器的版本

image.png

  • 使用编译器编译单个文件

image.png

  • 使用编译器编译链接多个文件

image.png

  • 查看预处理结果:gcc -E hello.c -o hello.i

image.png
image.png
hello.i文件中是hello.c的main函数部分,由此可以推断出前半部分是经过宏展开、头文件包含等预处理指令处理后产生的,其中包含了源代码中所有的宏定义、条件编译指令、头文件包含指令等。

  • 查看语法分析树:gcc -fdump-tree-all hello.c

image.png
在以下的文件我们看到了对printf和return的语法分析
image.png
gcc编译生成的语法分析树文件主要有两种,分别是AST(Abstract Syntax Tree)和GIMPLE(GNU Implementation of Minimal Programming Languages and Execution)。
AST是对源代码语法结构的一种抽象描述,它以树的形式表现了程序的语法结构,每个节点代表一个语法单元,如表达式、语句、函数等。AST通常是在语法分析之后生成的,它可以帮助编译器进行语义分析、优化和代码生成等工作。
GIMPLE是gcc编译器中的一种中间表示形式,它是对AST的进一步抽象和简化,是一种更为低级的表达形式。GIMPLE表示程序的语义含义,并且包含了足够的信息,使得编译器可以进行各种优化。GIMPLE文件通常是在gcc编译器的后端生成的,它可以用来分析编译器优化的效果,也可以用来进行代码生成。

  • 查看中间代码生成结果:Code generation result: gcc -fdump-rtl-all hello.c

image.png

  • 查看生成的目标代码(汇编代码):gcc –S hello.c –o hello.s

image.png

.text:表示代码段,其中包含了程序的可执行代码。
.file "hello.c":指示源文件名。
.globl main:将main函数声明为全局可见,以便可以从其他源文件中引用它。
.p2align 4, 0x90:指示对齐方式,此处的对齐方式为4字节对齐,并使用nop填充空隙。
.type main, @function:声明main函数是一个函数。
main::标记函数入口点。
.cfi_startproc:开始定义函数堆栈帧。
pushq %rbp:将栈底指针寄存器压入堆栈。
.cfi_def_cfa_offset 16:定义堆栈指针的初始偏移。
.cfi_offset %rbp, -16:定义rbp寄存器在堆栈帧中的偏移。
movq %rsp, %rbp:将栈顶指针复制到栈底指针。
.cfi_def_cfa_register %rbp:定义堆栈指针寄存器。
subq $16, %rsp:为局部变量在栈上分配16个字节的空间。
movabsq $.L.str, %rdi:将字符串地址(.L.str标签)保存到rdi寄存器中。
movl $0, -4(%rbp):将0保存到位于rbp寄存器的-4偏移处的单字节内存单元中。
movb $0, %al:将0保存到al寄存器中。
callq printf:调用printf函数。
xorl %ecx, %ecx:将ecx寄存器的值设置为0。
movl %eax, -8(%rbp):将eax寄存器的值保存到rbp寄存器的-8偏移处的4字节内存单元中。
movl %ecx, %eax:将ecx寄存器的值复制到eax寄存器中。
addq $16, %rsp:释放栈空间。
popq %rbp:从堆栈中弹出rbp寄存器。
retq:返回调用函数。
.Lfunc_end0::标记函数结束点。
.size main, .Lfunc_end0-main:指示main函数的大小。
.cfi_endproc:结束定义函数堆栈帧。
.type .L.str, @object:将.L.str标签声明为数据。
.section	.rodata.str1.1,"aMS",@progbits,1: 声明一个只读数据段,该段名称为.rodata.str1.1,包含了一个字符串
.L.str:  :声明一个标签名为.L.str,用于表示该字符串的地址
.asciz	"Hello, world.\n" :声明一个以'\0'结尾的字符串,即"C字符串"
.size	.L.str, 15   :声明该字符串的大小为15个字节
.ident	"clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)" :声明编译器的版本号
.section	".note.GNU-stack","",@progbits :声明一个无用的标记,用于保护堆栈不受某些攻击手段影响

LLVM 运行结果分析

  • 查看编译器的版本

image.png

  • 使用编译器编译单个文件

image.png

  • 使用编译器编译链接多个文件

image.png
image.png
可见O2优化后的程序运行速度比原来快了一倍
image.png

  • 查看编译流程和阶段:clang -ccc-print-phases test.c -c

image.png

  • 查看词法分析结果:clang test.c -Xclang -dump-tokens

生成了2524行结果,以下是部分结果
image.png
Loc=XXXX 表示该字符或字符串所对应的行数及位置
Int ‘int’ , 表⽰ int 代表整型
Identifier ‘main’, 表⽰ main 是⼀个标识符
numeric_constant ‘XXXXXX’,表⽰数值常量
l_paren ‘(‘ , 表示左括号
Clang词法分析器(Lexer)的主要结构是一个有限状态自动机(Finite State Automaton,FSA),它根据输入的源代码逐个读入字符,识别出每个Token并返回给编译器前端。Clang的词法分析器的主要任务是将输入的源代码转换为Token序列,以便进行下一步的语法分析。
Clang的词法分析器由一个Token类(Token)、一个词法分析器类(Lexer)以及一些辅助类组成。Token类表示一个Token,它存储了Token的种类、字符串值、Token在源代码中的位置等信息。词法分析器类Lexer负责将源代码转换为Token序列,Lexer内部维护一个有限状态自动机,根据当前的状态和输入字符,识别出当前的Token并返回给前端。
除了Token和Lexer外,Clang的词法分析器还涉及到一些辅助类,例如字符编码转换类、标识符表(IdentifierTable)、关键字表(KeywordTable)等。字符编码转换类用于将输入字符转换为内部字符编码,IdentifierTable和KeywordTable用于存储标识符和关键字的信息,并根据标识符或关键字的字符串查找对应的Token种类。
Clang词法分析输出的主要结构是词法单元(Token),它表示源代码中的一个单独的语法元素,如标识符、关键字、运算符、分隔符等等。每个词法单元都有一个对应的TokenKind,表示该单元的类型,以及对应的源代码位置信息。
在Clang中,词法分析器的输出通常由Token组成的Token序列,也称为Token流。这个Token流是Clang进行下一步语法分析的输入,可以被转换成AST(抽象语法树)。
在Clang的源码中,Token的定义和实现在文件"clang/Basic/TokenKinds.def"中,Token的输出和处理在"clang/Lex/Lexer.cpp"和"clang/Lex/Token.h"中实现。

  • 查看词法分析结果 2:clang test.c -Xclang -dump-raw-tokens

生成了71行结果,以下是部分结果
image.png
可以看到词法分析2对空格‘ ’也同样进行了分析。

  • 查看语义分析结果:clang test.c -Xclang -ast-dump

生成了755行结果,以下是部分结果
image.png
通过命令行参数-ast-dump,可以将源代码的抽象语法树以一定的结构输出到终端。这个输出的结构比较详细,包括了每个节点的类型、内容、子节点等信息。
使用如下代码可以看到其抽象语法树

clang -Xclang -ast-dump -fsyntax-only test.c

例如下图为部分结果
image.png

  • 查看语义分析结果 2:clang test.c -Xclang -ast-view

生成了11行结果,以下是部分结果
image.png

  • 查看编译优化的结果:clang test.c -S -mllvm -print-after-all

生成了678行结果,以下是部分结果
image.png
clang编译优化的主要阶段和对应的结构:

  1. 词法分析和语法分析阶段:将源代码转换为抽象语法树(AST)表示。
  2. 语义分析阶段:对AST进行类型检查和语义分析,生成符号表。
  3. LLVM IR生成阶段:将AST转换为LLVM IR(Intermediate Representation)表示。
  4. 优化阶段:对LLVM IR进行各种优化,包括常量折叠、函数内联、循环展开等。
  5. 后端代码生成阶段:将LLVM IR转换为目标机器代码表示,生成可执行文件。

在优化阶段,LLVM提供了多种优化选项,可以通过命令行参数进行控制。常用的优化选项包括:

  1. -O0/-O1/-O2/-O3:控制编译器的优化级别,-O0表示不进行优化,-O1/-O2/-O3分别表示优化级别1/2/3,优化级别越高,优化越多,但编译时间也会相应增加。
  2. -funroll-loops:循环展开优化,可以将循环展开成多个相同的代码块,以减少循环的开销。
  3. -finline-functions:函数内联优化,可以将函数调用替换为函数体,以减少函数调用的开销。
  4. -fomit-frame-pointer:省略函数堆栈帧指针,以减少函数调用的开销。
  5. -march/-mcpu:针对特定的目标机器进行优化,可以通过指定目标机器的架构和CPU型号来控制优化选项。
  • 查看生成的目标代码结果:Target code generation:clang test.c –S

以下是部分结果
image.png
左边是生成的汇编代码,可以看到规模很大,而且确实不是人能想出来的代码。

GCC 与 LLVM 对比分析

GCC和LLVM都是流行的C语言编译器,它们在编译C语言代码时有一些不同的情况和特点。
image.png
GCC 各个优化选项运行时间五次测量取均值
image.png
LLVM 各个优化选项运行时间五次测量取均值
image.png
image.png
GCC在运行时间较短的循环法解汉诺塔问题时是比LLVM的程序运行速度快的。然而在运行矩阵运算时却是LLVM常常是程序运行速度更快的那个。
GCC 各个优化选项运行时间五次测量取均值
image.png
LVM 各个优化选项运行时间五次测量取均值
image.png
image.png
综上所述,相同代码在GCC和LLVM编译出的程序运行速度不同,而具体哪个编译器编译出的程序运行速度快还是与代码具体情况有关

  • GCC和LLVM编译链接单个文件时的一些主要区别

优化级别:GCC和LLVM在编译时都支持优化选项,但它们在优化级别上略有不同。GCC的默认优化级别是O0,这意味着它不会执行任何优化。而LLVM的默认优化级别是O2,它执行了一些中等优化。因此,如果需要对代码进行优化,则可能需要在GCC中设置优化级别。
内部架构:GCC和LLVM的内部架构也有所不同。GCC采用传统的编译器设计,将代码分成几个阶段处理,包括预处理、词法分析、语法分析、代码生成和优化等。而LLVM采用的是基于IR(中间表示)的设计,可以在多个阶段中重复使用IR,从而提高编译效率和代码质量。
预处理:GCC和LLVM在处理预处理器指令时有所不同。GCC默认情况下会处理预处理器指令,并将其包含的头文件替换为实际的代码。而LLVM默认情况下不会处理预处理器指令,但可以使用clang -E选项来启用预处理器。
标准库:GCC和LLVM使用不同的标准库。GCC通常使用GNU C库(glibc),而LLVM使用LLVM libc++和LLVM libc。这些库在实现上可能有所不同,因此在某些情况下,可能需要根据具体情况选择使用哪个标准库。
编译速度:由于LLVM采用了基于IR的设计,可以在多个阶段中重复使用IR,从而提高编译效率和代码质量,因此LLVM通常比GCC编译速度更快。

  • 链接多个文件时的严格程度

因为我在链接复杂代码的多个文件时用GCC失败了,用LLVM编译成功。所以对此进行了考察:
在大多数情况下,GCC和LLVM的编译链接多个文件的严格程度是相似的,因为它们都遵循C语言的规范和标准,按照预定的方式处理源文件、头文件和库文件之间的依赖关系。
然而,在某些特殊情况下,GCC和LLVM的编译链接可能会有所不同。例如,当链接静态库时,GCC和LLVM对未使用的符号的处理方式可能会有所不同。GCC会保留未使用的符号,而LLVM会将未使用的符号从目标文件中删除。这意味着在使用GCC时,未使用的符号仍然存在于生成的目标文件中,而使用LLVM时,则可能不会出现这些未使用的符号。
此外,GCC和LLVM也可能会有不同的优化策略和默认设置,这可能会导致它们对编译链接多个文件时的严格程度有所不同。例如,GCC默认启用某些优化选项,可能会导致它更加严格,而LLVM默认不启用这些选项,可能会导致它更加宽松。
总之,在大多数情况下,GCC和LLVM的编译链接多个文件时的严格程度是相似的,但在某些特殊情况下,它们可能会有所不同。在实际应用中,需要根据具体情况选择合适的编译器,并注意编译器的优化选项和默认设置可能会对编译链接产生影响。

  • 词法分析和语义分析

gcc不直接提供输出词法分析和语义分析结果的功能,但可以通过使用特定的选项和工具来实现。
对于词法分析,gcc可以通过-E选项将源代码预处理后输出到标准输出,此时可以查看预处理后的代码来检查词法分析结果。
对于语义分析,gcc可以使用-fsyntax-only选项只进行语法和语义分析,并不生成目标代码。如果源代码中存在语法和语义错误,gcc会输出错误信息到标准错误流。
而clang可以输出词法分析和语义分析的结果。
对于词法分析,clang提供了-dump-tokens选项,该选项将输出源文件中的所有Token信息。
对于语义分析,clang提供了多种选项来输出不同形式的AST(抽象语法树),包括-ast-dump、-ast-print、-ast-view等。
除了上述选项之外,clang还提供了许多其他选项和工具,可用于输出其他形式的语法和语义分析结果,例如,-verify选项可用于检查代码是否符合C语言规范,-emit-llvm选项可用于将代码转换为LLVM IR等。
总之,clang提供了多种选项和工具,可以方便地输出词法分析和语义分析的结果。

  • 同一个代码GCC生成的汇编语言显然比LLVM的简洁:

gcc生成的汇编语言
LLVM生成的汇编语言
除此之外,他们还在其他方面有不同。

  1. 编译速度:在编译速度方面,GCC通常比LLVM快。这是因为GCC在编译时将源代码直接转换为汇编代码,而LLVM将源代码转换为LLVM IR(Intermediate Representation)中间代码,然后再将中间代码转换为汇编代码。这个额外的中间步骤会导致编译速度的降低,但同时也带来了LLVM更高的优化能力。
  2. 优化能力:LLVM具有更强大的优化能力,因为它将源代码转换为中间代码后,可以进行更精细的优化和分析,从而生成更高效的汇编代码。而GCC的优化能力相对较弱,因为它直接将源代码转换为汇编代码,无法进行更精细的优化和分析。
  3. 跨平台支持:LLVM具有更好的跨平台支持能力,它可以生成多种不同平台的汇编代码和机器码,同时也可以在多种不同平台上运行。而GCC的跨平台支持能力相对较弱,虽然也可以生成多种不同平台的汇编代码和机器码,但是在不同平台上运行时需要进行重新编译。
  4. 可扩展性:LLVM具有更好的可扩展性,因为它将编译器的前端和后端进行了解耦,可以在不同的前端和后端之间切换。例如,LLVM可以用作C++、Objective-C和Swift等语言的编译器,也可以用于生成GPU和DSP等嵌入式设备的汇编代码。而GCC的可扩展性相对较弱,虽然也可以支持多种不同语言和平台,但是需要对编译器进行相应的修改。

综上所述,GCC和LLVM都是优秀的C语言编译器,它们在编译速度、优化能力、跨平台支持和可扩展性等方面存在差异。具体使用哪个编译器应该根据具体的需求和情况来选择。

实验心得体会

①通过本实验,我了解了编译过程中的每个过程⽣成的⽂件,
②了解了编译器的常⽤选项,包括⼀些优化参数
③通过使用不同的编译器,调用不同的优化参数,体会到这些参数对程序运行带来的不同影响

附录

#include<stdio.h>
int main(void)
{
    #ifdef MY_MACRO
    printf("\n Macro defined \n");
    #endif
    char c = -10;
    // Print the string
    printf("\n The Geek Stuff [%d]\n", c);
    return 0;
}
#include<stdio.h>
int main(void)
{
    printf("Hello, world.\n");
    return 0;
}
#include<stdio.h>
#include<malloc.h>
#include <time.h>
typedef int ElemType;
typedef struct stack
{
	ElemType tray;
	struct stack *next;
}stack;
stack *top[3];

stack *CreateStack()
{
	stack *top;
	top = (stack *)malloc(sizeof (stack));
	top->tray = 65;
	top->next = NULL;
	return(top);
}

/*
*功能:进栈
*参数:进栈无素,栈顶指针
*返回:栈顶指针
*/
stack *push(int e, stack *top)
{
	stack *p;
	p = top;
	top = (stack *)malloc(sizeof (stack));
	top->next = p;
	top->tray = e;
	return(top);
}

/*
*功能:出栈
*参数:栈顶指针
*返回:栈顶指针
*/
stack *pop(stack *top)
{
	stack *p;
	p = top;
	top = top->next;
	free(p);
	return(top);
}


/*
*功能:移动盘子并输出移动路径
*参数:盘子总数
*/
void hanoi(int TrayAmount)
{
	int sub, n, i, LowTray = 0;
	n = TrayAmount % 2;//TrayAmount为偶数时n = 0;TrayAmount为奇数时n = 1
	while (!(top[0]->tray == 65 && top[1]->tray == 65))//当top[0]、top[1]上没有盘子时停止
	{
		i = LowTray;
		sub = i + 1 + n;
		sub = sub % 3;
		top[sub] = push(top[i]->tray, top[sub]);//移动1号盘
		top[i] = pop(top[i]);
		// printf("Move tray 1 :%d-->%d\n", i, sub);
		sub = i + 2 - n;
		sub = sub % 3;
		if (top[i]->tray < top[sub]->tray)
		{
			top[sub] = push(top[i]->tray, top[sub]);
			// printf("Move tray %d :%d-->%d\n",top[i]->tray, i, sub);
			top[i] = pop(top[i]);
		}

		else if (top[i]->tray > top[sub]->tray)
		{
			top[i] = push(top[sub]->tray, top[i]);
			// printf("Move tray %d :%d-->%d\n", top[i]->tray, sub, i);
			top[sub] = pop(top[sub]);
		}
		LowTray = LowTray + n + 1;
		LowTray = LowTray % 3;
	}
}

#include<stdio.h>
#include<malloc.h>
#include <time.h>

typedef int ElemType;
typedef struct stack
{
	ElemType tray;
	struct stack *next;
}stack;
stack *top[3];
//TrayAmount盘子总数;LowTray是最小盘子(1号)所在柱子的标号



int main()
{    
	clock_t start, end;
    double cpu_time_used;
    start = clock();

	int i = 0, TrayAmount;
	top[0] = CreateStack();
	top[1] = CreateStack();
	top[2] = CreateStack();
	// printf("Enter TrayAmount:\nTrayAmount=");
	TrayAmount = 20;
	for (i = TrayAmount; i > 0; i-- )//盘子都放在柱子0上
	{
		top[0] = push(i, top[0]);
	}	
	hanoi(TrayAmount);

	end = clock();
    cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
    printf("Program Running Time: %f s\n", cpu_time_used);
}

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

//move中的实参与hanoi函数中的形参相对应,而hanoi函数中形参a,b,c所对应的值也是在有规律的变化
int main()
{
	int n = 4;
	hanoi(n, 'A', 'B', 'C');
	return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

void move(int x, int y)
{
	printf("%c->%c\n", x, y);
}
void hanoi(int n, char a, char b, char c)
{
	if (n == 1)
	{
		move(a, c);
	}
	else
	{
		hanoi(n - 1, a, c, b);//将A座上的n-1个盘子借助C座移向B座
		move(a, c);//将A座上最后一个盘子移向C座
		hanoi(n - 1, b, a, c);//将B座上的n-1个盘子借助A座移向C座
	} 
}
#include<stdio.h>
#include<malloc.h>
#include <time.h>
typedef int ElemType;
typedef struct stack
{
	ElemType tray;
	struct stack *next;
}stack;
stack *top[3];

stack *CreateStack()
{
	stack *top;
	top = (stack *)malloc(sizeof (stack));
	top->tray = 65;
	top->next = NULL;
	return(top);
}

/*
*功能:进栈
*参数:进栈无素,栈顶指针
*返回:栈顶指针
*/
stack *push(int e, stack *top)
{
	stack *p;
	p = top;
	top = (stack *)malloc(sizeof (stack));
	top->next = p;
	top->tray = e;
	return(top);
}

/*
*功能:出栈
*参数:栈顶指针
*返回:栈顶指针
*/
stack *pop(stack *top)
{
	stack *p;
	p = top;
	top = top->next;
	free(p);
	return(top);
}


/*
*功能:移动盘子并输出移动路径
*参数:盘子总数
*/
void hanoi(int TrayAmount)
{
	int sub, n, i, LowTray = 0;
	n = TrayAmount % 2;//TrayAmount为偶数时n = 0;TrayAmount为奇数时n = 1
	while (!(top[0]->tray == 65 && top[1]->tray == 65))//当top[0]、top[1]上没有盘子时停止
	{
		i = LowTray;
		sub = i + 1 + n;
		sub = sub % 3;
		top[sub] = push(top[i]->tray, top[sub]);//移动1号盘
		top[i] = pop(top[i]);
		// printf("Move tray 1 :%d-->%d\n", i, sub);
		sub = i + 2 - n;
		sub = sub % 3;
		if (top[i]->tray < top[sub]->tray)
		{
			top[sub] = push(top[i]->tray, top[sub]);
			// printf("Move tray %d :%d-->%d\n",top[i]->tray, i, sub);
			top[i] = pop(top[i]);
		}

		else if (top[i]->tray > top[sub]->tray)
		{
			top[i] = push(top[sub]->tray, top[i]);
			// printf("Move tray %d :%d-->%d\n", top[i]->tray, sub, i);
			top[sub] = pop(top[sub]);
		}
		LowTray = LowTray + n + 1;
		LowTray = LowTray % 3;
	}
}
int main()
{    
	clock_t start, end;
    double cpu_time_used;
    start = clock();

	int i = 0, TrayAmount;
	top[0] = CreateStack();
	top[1] = CreateStack();
	top[2] = CreateStack();
	// printf("Enter TrayAmount:\nTrayAmount=");
	TrayAmount = 20;
	for (i = TrayAmount; i > 0; i-- )//盘子都放在柱子0上
	{
		top[0] = push(i, top[0]);
	}	
	hanoi(TrayAmount);

	end = clock();
    cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
    printf("Program Running Time: %f s\n", cpu_time_used);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

typedef int** Content;

typedef struct _matrix_
{
    int row;
    int col;
    Content content;
}Matrix;

void initMat(Matrix& mat)
{
    mat.content = (int**)malloc(sizeof(int*) * mat.row);
    for (int row = 0; row < mat.row; row++)
    {
        mat.content[row] = (int*)malloc(sizeof(int) * mat.col);
        memset(mat.content[row], 0, sizeof(int) * mat.col);
    }
}

void delMat(Matrix& mat)
{
    free(mat.content);
}

void showContent(const Matrix& mat)
{
    for (int row = 0; row < mat.row; row++)
    {
        for (int col = 0; col < mat.col; col++)
        {
            printf("%d ", mat.content[row][col]);
        }
        printf("\n");
    }
}

void multiplyMat(const Matrix& matA, const Matrix& matB, Matrix& matC)
{

    for (int a_row = 0; a_row < matA.row; a_row++)
    {
        for (int a_col = 0; a_col < matA.col; a_col++)
        {
            for (int b_col = 0; b_col < matB.col; b_col++)
            {
                matC.content[a_row][b_col] += matA.content[a_row][a_col] * matB.content[a_col][b_col];
            }
        }
    }
}

double Clock()
{
    return clock();
}

int main()
{
    clock_t start, finish;

    Matrix matA, matB, matC;
    matA.row = 500, matA.col = 500;
    matB.row = 500, matB.col = 500;

    if (matA.col != matB.row)
    {
        printf("矩阵大小不符合要求\n");
        return 1;
    }
    
    matC.row = matA.row;
    matC.col = matB.col;

    initMat(matA);
    initMat(matB);
    initMat(matC);


    for (int i = 0; i < 500; i++)
    {
        for (int j = 0; j < 500; j++)
        {
            matA.content[i][j] = j;
            matB.content[i][j] = j;
        }
    }

    start = Clock();
    multiplyMat(matA, matB, matC);
    finish = Clock();

    printf("耗时: %lf s\n", (double)(finish - start)/1000000);

    delMat(matA);
    delMat(matB);
    delMat(matC);

    return 0;
}
posted @ 2023-08-09 06:44  LateSpring  阅读(266)  评论(0编辑  收藏  举报