实验目的和内容:
了解工业界常用的编译器 GCC 和 LLVM,熟悉编译器的安装和使用过程,观察编译器工作过程中生成的中间文件的格式和内容,了解编译器的优化效果,为编译器的学习和构造奠定基础。
实验过程与步骤:
本实验主要的内容为在 Linux 平台上安装和运行工业界常用的编译器GCC和LLVM,如果系统中没有安装,则需要首先安装编译器,安装完成后编写简单的测试程序,使用编译器编译,并观察中间输出结果。
(1)对于 GCC 编译器,完成编译器安装和测试程序编写后,按如下步骤完成:
1.查看编译器的版本
2.使用编译器编译单个文件
3.使用编译器编译链接多个文件
4.查看预处理结果:gcc -E hello.c -o hello.i
5.查看语法分析树:gcc -fdump-tree-all hello.c
6.查看中间代码生成结果:gcc -fdump-rtl-all hello.c
7.查看生成的目标代码(汇编代码):gcc –S hello.c –o hello.s
(2)对于 LLVM 编译器,完成编译器安装和测试程序编写后,按如下步骤完成:
1.查看编译器的版本
2.使用编译器编译单个文件
3.使用编译器编译链接多个文件
4.查看编译流程和阶段:clang -ccc-print-phases test.c -c
5.查看词法分析结果:clang test.c -Xclang -dump-tokens
6.查看词法分析结果 2:clang test.c -Xclang -dump-raw-tokens
7.查看语法分析结果:clang test.c -Xclang -ast-dump
8.查看语法分析结果 2:clang test.c -Xclang -ast-view
9.查看编译优化的结果:clang test.c -S -mllvm -print-after-all
10.查看生成的目标代码结果:clang test.c –S
(3)要求:
编写测试程序:可以使用语言认知部分的 C 语言程序,或者其他的 C 语言程序作为编译器的输入进行观测。需要注意的是,不但要求对单个C文件进行编译,还要求对多个C文件一起编译和链接。
运行编译器进行观测:分别运行 GCC 和 LLVM 编译器,首先要学习编译器的基本使用方法,其次要通过编译选项查看分析编译器的中间结果,及其与输入源码之间的对应关系,最后使用-O0、-O1、-O2和-O3分别使用两个编译器对输入程序进行优化编译,并对比 GCC 和 LLVM 优化后程序的运行效率。
运行效果截图
准备环境
GCC编译
查看编译器的版本
使用编译器编译单个文件
//main.c
#include <stdio.h>
int main()
{
int a;
a = 1;
a++;
printf("%d\n", a);
return 0;
}
4.2.3使用编译器编译链接多个文件
//a.h
#include <stdio.h>
int a = 1;
//a.c
#include <stdio.h>
#include "a.h"
int main()
{
printf("a = %d\n", a);
return 0;
}
4.2.4查看预处理结果:gcc -E hello.c -o hello.i
4.2.5查看语法分析树:gcc -fdump-tree-all hello.c
4.2.6查看中间代码生成结果:gcc -fdump-rtl-all hello.c
4.2.7查看生成的目标代码(汇编代码):gcc –S hello.c –o hello.s
.file "main.c"
.text
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $1, -4(%rbp)
addl $1, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 8.4.1 20200928 (Red Hat 8.4.1-1)"
.section .note.GNU-stack,"",@progbits
llvm 编译
查看编译器的版本
使用编译器编译单个文件
使用编译器编译链接多个文件
查看编译流程和阶段: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
查看生成的目标代码结果:clang test.c –S
.text
.file "main.c"
.globl main # -- Begin function main
.p2align 4, 0x90
.type main,@function
main: # @main
.cfi_startproc
# %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -4(%rbp)
movl $1, -8(%rbp)
movl -8(%rbp), %eax
addl $1, %eax
movl %eax, -8(%rbp)
movl -8(%rbp), %esi
movabsq $.L.str, %rdi
movb $0, %al
callq printf
xorl %eax, %eax
addq $16, %rsp
popq %rbp
.cfi_def_cfa %rsp, 8
retq
.Lfunc_end0:
.size main, .Lfunc_end0-main
.cfi_endproc
# -- End function
.type .L.str,@object # @.str
.section .rodata.str1.1,"aMS",@progbits,1
.L.str:
.asciz "%d\n"
.size .L.str, 4
.ident "clang version 12.0.1 (Red Hat 12.0.1-4.module_el8.5.0+1025+93159d6c)"
.section ".note.GNU-stack","",@progbits
.addrsig
.addrsig_sym printf
优化程序运行效率对比
使用-O0、-O1、-O2和-O3分别使用两个编译器对输入程序进行优化编译,并对比 GCC 和 LLVM 优化后程序的运行效率。
使用一个纯c语言的矩阵乘法来比较
#include <sys/time.h>
#include <stdio.h>
int main()
{
struct timeval start, end;
gettimeofday(&start, NULL);
int N = 100;
int matrix_one[N][N];
int matrix_two[N][N];
int matrix_result[N][N];
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
matrix_one[i][j] = (j + 1);
}
}
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
matrix_two[i][j] = (j + 1);
}
}
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
matrix_result[i][j] = 0;
for (int k = 0; k < N - 1; k++)
{
matrix_result[i][j] += matrix_one[i][k] * matrix_two[k][j];
}
}
}
gettimeofday(&end, NULL);
long time_lag = end.tv_usec - start.tv_usec;
printf("time : %d", time_lag);
return 0;
}
4.4.1 GCC优化
4.4.2 clang优化
Clang显的更为严谨一些,long类型要求我们修改为ld,修改之后再来编译:
优化后时间已经近乎瞬时完成了