gcc编译器背后的故事
一、gcc生成使用.a静态库与.so动态库
1)创建一个新文件夹test1
mkdir test1
进入文件夹test1
cd test1
使用vim命令生成hello.h,hello.c,main.c三个文件
vim hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif//HELLO_H
vim hello.c
#include<stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n",name):
}
vim main.c
#include"hello.h"
int main()
{
hello("everyone");
return 0;
}
2)将hello.c用gcc编译成.o文件
gcc -c hello.c
3)由.o文件生成静态库
ar -crv libmyhello.a hello.o
4)在程序中使用静态库
gcc -o hello main.c -L. -lmyhello
运行程序
./hello
尝试删除静态库函数后再运行程序试试
rm libmyhello.a
发现程序依旧能运行,说明静态库中的公用函数hello已经连接到目标文件hello中了。
5)由.o文件生成动态库
gcc -shared -fPIC -o libmyhello.so hello.o
6)在程序中使用动态库
./hello
结果程序出错了
原来是程序在运行时,会在/usr/lib和/lib等目录中寻找需要的动态库文件。找到了,则载入动态库;找不到,则会提示类似上述错误而终止程序运行。
将文件libmyhello.so复制到目录/user/lib中,并再次运行。
sudo mv libmyhello.so /usr/lib
运行成功,也进一步说明动态库在程序运行时是需要的。
二、c语言程序的静态库和动态库
1.自定义c语言程序
在test文件夹中分别创建sub1.c,sub2.c,main1.c三个文件
sub1.c:包含函数x2x(两数相除)
float x2x(int a,int b)
{
return a/b;
}
sub2.c:包含函数x2y(两数相乘)
float x2y(int a,int b)
{
return a*b;
}
main1.c:包含main函数
#include<stdio.h>
#include"sub1.c"
#include"sub2.c"
int main()
{
int a=4,b=2;
printf("a= %d b= %d\n",a,b);
printf("x2x:a/b= %f\n",x2x(a,b));
printf("x2y:a*b= %f\n",x2y(a,b));
return 0;
}
2.生成静态库与可执行程序文件
使用gcc编译sub1.c和sub2.c得到.o文件
gcc -c sub1.c sub2.c
使用ar命令生成静态库
ar -crv libsub1.a sub1.o
ar -crv libsub2.a sub2.o
在程序中使用静态库
gcc main1.c libsub1.a libsub2.a -o main2
运行程序main2
3.生成动态库与可执行程序文件
使用gcc命令生成动态库
gcc -shared -fPIC -o libsub1.so sub1.o
gcc -shared -fPIC -o libsub2.so sub2.o
在程序中使用动态库
gcc main1.c libsub1.so libsub2.so -o main3
将两个.so文件移动到目录/usr/lib 中去
sudo mv libsub1.so /usr/lib
sudo mv libsub2.so /usr/lib
运行程序
三、Linux gcc的编译过程
在test3目录下创建一个简单的c语言程序文件test.c
#include<stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
其实这个程序一步到位的编译指令是gcc test.c -o test
实质上其编译过程分为4个阶段:预处理/预编译→编译→汇编→连接
1)预处理/预编译
将源文件 hello.c 文件预处理生成 hello.i
gcc -E test.c -o test.i
2)编译
将预处理生成的 hello.i 文件编译生成汇编程序 hello.s
gcc -S test.i -o test.s
3)汇编
将编译生成的 hello.s 文件汇编生成目标文件 hello.o
// 用gcc进行汇编
gcc -c hello.s -o hello.o
//用as进行汇编
as -c hello.s -o hello.o
4)连接
分为静态链接和动态链接,生成可执行文件
//动态链接
gcc hello.c -o hello
//静态链接
gcc -static hello.c -o hello
5)程序运行
四、gcc编译工具集
1.了解ELF文件
1)分析ELF文件
ELF文件包括下面几个段
- .test:已编译程序的指令代码段。
- .rodata:ro代表read only,即制度数据(譬如常熟const)。
- .data:已初始化的c程序全局变量和静态局部变量
- .bss:未初始化的c程序全局变量和静态局部变量
- .dbug:调试符号表,调试器用此段的信息帮助调试
可以使用readelf -S命令查看各个section的信息
readelf -S test
2)反汇编ELF
由于ELF文件无法被当做普通文本文件打开,如果希望直接查看一个ELF文件包含的命令和数据,需要使用反汇编的方法。
使用objdump -D命令对其进行反汇编
objdump -D test
2.as汇编编译器
1)首先是下载安装nasm编译器
下载链接: https://www.nasm.us/pub/nasm/releasebuilds/2.14rc16/nasm-2.14rc16.tar.gz
将链接复制到ubuntu内的火狐浏览器进行下载,下载完成后查看文件地址
然后观察路径,发现压缩包在/tmp/mozilla_huo0内
控制版中依次执行以下命令进入文件夹
cd /
cd tmp
cd mozilla_huo0
找到下载的安装包了,解压
tar zxvf nasm-2.14rc16.tar.gz
解压完成后依次执行以下命令进行安装
cd nasm-2.14rc16/
./configure
make
sudo make install
查看是否安装成功
nasm -version
2)编译汇编文件 hello.asm
新建文件hello.asm,代码如下:
; hello.asm
section .data ; 数据段声明
msg db "Hello, world!", 0xA ; 要输出的字符串
len equ $ - msg ; 字串长度
section .text ; 代码段声明
global _start ; 指定入口函数
_start: ; 在屏幕上显示一个字符串
mov edx, len ; 参数三:字符串长度
mov ecx, msg ; 参数二:要显示的字符串
mov ebx, 1 ; 参数一:文件描述符(stdout)
mov eax, 4 ; 系统调用号(sys_write)
int 0x80 ; 调用内核功能
; 退出程序
mov ebx, 0 ; 参数一:退出代码
mov eax, 1 ; 系统调用号(sys_exit)
int 0x80 ; 调用内核功能
编译
nasm -f elf64 hello.asm
链接:
ld -s -o hello hello.o
运行程序
3)对比汇编与C代码的编译生成的可执行程序文件
C语言:
汇编:
发现直接由汇编生成的程序文件要更小。
五、linux环境下运行c语言小游戏
1)实现小游戏前要先下载curses库
curses库是UNIX下的一套专门用来处理终端界面下的光标移动以及屏幕显示的一个函数库。
sudo apt-get install libncurses5 libncurses5-dev
2)创建小游戏程序文件,并对其进行编译
gcc mysnake1.0.c -lcurses -o mysnake
3)运行小游戏
六、实验总结
此次实验加深了解了gcc编译器,学会了用gcc命令生成并使用静态库和动态库,且都生成了可执行程序文件。除此之外,也有了解到编译工具集中个软件的用途,比如ELF文件格式和汇编语言格式。最后也体验到了linux环境下的c语言小游戏,对curses库有一个初步的认识和了解。