Linux系统中可执行程序的组装、gcc编译工具和库函数的执行过程
一、可执行程序组装过程
源程序到可执行程序的4个过程:预处理、编译、汇编、链接。
预处理:根据预处理指令组装新的C/C++源程序,又叫预编译,是完整编译过程的第一个阶段,在正式的编译阶段之前进行。
编译:将预处理完的文件进行一系列词法分析、语法分析、语义分析、中间代码生成、目标代码生成和优化后,产生相应的汇编代码文件。
汇编:将编译产生的汇编代码文件转变成可执行的机器指令,并生成可重定位目标程序的.o文件,该文件为二进制文件,字节编码是机器指令。
链接:分为静态链接和动态链接,通过链接器将多个目标文件和库文件链接在一起生成一个完整的可执行程序。
二、用gcc生成静态库和动态库
1、编辑生成例子程序 hello.h、hello.c 和 main.c
(1)先创建一个目录,保存文件。
mkdir test1
(2)输入命令进入创建目录。
cd test1
(3)用 vim文本编辑器编辑生成所需要的文件。
创建并编辑第一个文件命令如下:
vim hello.h
具体代码如下:
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
注意:进入vim,开始编辑需要点击键盘“s”,编辑完成后,按键盘“Esc”,再输入“:wq”,表示退出并保存。
创建并编辑第二个文件命令如下:
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 编译成.o 文件
(1)输入命令使得.c文件变为.o文件,无论静态库,还是动态库,都是由.o 文件创建的。具体命令如下:
gcc -c hello.c
(2)运行 ls 命令看是否生存了 hello.o 文件。
ls
可以看到如下结果(不难发现已经生成了hello.o文件):
3、由.o 文件创建静态库
(1)静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a,创建静态库用 ar 命令,具体命令如下:
ar -crv libmyhello.a hello.o
(2)运行 ls 命令看看是否生存了libmyhello.a 文件。
ls
可以看到如下结果(不难发现已经生成了libmyhello.a文件):
4、在程序中使用静态库
静态库制作完了,使用它内部的函数,只需要在使用到这些公用函数的源程序中 含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。
注意:gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。
在程序 3:main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main中直接调用公用函数 hello。下面先生成目标程序 hello,运行 hello 程序。
方法一:
(1)输入命令如下:
gcc -o hello main.c -L. –lmyhello
注意:自定义的库时,main.c 还可放在-L.和 –lmyhello 之间,但是不能放在它俩之后,否则会提 示 myhello 没定义,但是是系统的库时,如 g++ -o main(-L/usr/lib) -lpthread main.cpp 就不出错。
(2)运行程序,输入以下命令:
./hello
可以看到如下结果(输出“”“Hello everyone!”正确结果):
方法二:
(1)输入命令如下:
gcc main.c libmyhello.a -o hello
(2)运行程序,输入以下命令:
./hello
可以看到如下结果(输出“”“Hello everyone!”正确结果):
方法三:
(1)先生成main.o文件,输入以下命令:
gcc -c main.c
(2)再生成可执行文件,输入以下命令:
gcc -o hello main.o libmyhello.a
(3)运行程序,输入以下命令:
./hello
可以看到如下结果(输出“”“Hello everyone!”正确结果):
删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中
(1)输入如下命令:
rm libmyhello.a
(2)输入命令查看:
ls
(3)输入命令查看执行结果:
./hello
程序照常运行,静态库中的公用函数已经连接到目标文件中了。 我们继续看看如何在 Linux 中创建动态库。我们还是从.o 文件开始。
5、由.o 文件创建动态库文件
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。
(1)输入以下命令得到动态库文件 libmyhello.so:
gcc -shared -fPIC -o libmyhello.so hello.o
注意:如果如下出现错误,因为AMD64位,需要将 gcc -c hello.c 换成 gcc -fpic -c hello.c,或者直接输入gcc -fpic -c hello.c命令,再输入上述命令即可正确执行。
(2)样使用 ls 命令看看动态库文件是否生成:
ls
结果如下图所示:
6、在程序中使用动态库
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含 这些公用函数的原型声明,然后用 gcc 命令生成目标文件时指明动态库名进行编译。
(1)运行 gcc 命令生成目标文件,再运行:
gcc -o hello main.c -L. -lmyhello
(2)运行程序:
./hello
我们会发现出错了!!!快看看错误提示,原来是找不到动态库文件 libmyhello.so。程序在运行时, 会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。
(3)将文件 libmyhello.so 复制到目录/usr/lib 中:
mv libmyhello.so /usr/lib
(4)再运行程序:
./hello
结果如下:
三、静态库.a与.so库文件的生成与使用
1、创建目录并编辑文件
(1)先创建一个目录,保存文件。
mkdir test2
(2)输入命令进入创建目录。
cd test2
(3)用 vim文本编辑器编辑生成所需要的文件。
创建并编辑第一个文件命令如下:
vim A1.c
具体代码:
#include <stdio.h>
void print1(int arg)
{
printf("A1 print arg:%d\n",arg);
}
创建并编辑第二个文件命令如下:
vim A2.c
具体代码:
#include <stdio.h>
void print2(char *arg)
{
printf("A2 printf arg:%s\n", arg);
}
创建并编辑第三个文件命令如下:
vim A.h
具体代码:
#ifndef A_H
#define A_H
void print1(int);
void print2(char *);
#endif
创建并编辑第四个文件命令如下:
vim test.c
具体代码:
#include <stdlib.h>
#include "A.h"
int main()
{
print1(1);
print2("test");
exit(0);
}
2、静态库.a 文件的生成与使用
(1)生成目标文件(xxx.o)。
gcc -c A1.c A2.c
(2)生成静态库.a 文件。
ar crv libafile.a A1.o A2.o
(3)使用.a 库文件,创建可执行程序。
gcc -o test test.c libafile.a
(4)运行程序。
./test
结果如图所示(可以得到正确结果,其中创建可执行程序时有警告,可以不用修改程序):
3、共享库.so 文件的生成与使用
(1)生成目标文xxx.o。
此处生成.o 文件必须添加"-fpic"(小模式,代码少),否则在生成.so 文件时会出错
gcc -c -fpic A1.c A2.c
(2)生成共享库.so 文件。
gcc -shared *.o -o libsofile.so
(3)使用.so 库文件,创建可执行程序。
gcc -o test test.c libsofile.so
(4)运行程序。
./test
结果如下:
稳住不慌!!!这是由于 linux 自身系统设定的相应的设置的原因,即其只在/lib and /usr/lib 下搜索对应 的.so 文件,故需将对应 so 文件拷贝到对应路径。
(5)输入以下命令即可解决。
sudo cp libsofile.so /usr/lib
会提示输入密码哟
(6)再次运行程序。
./test
再看看结果如何(没有报错,并且得到正确结果):
4、目标文件与静态库文件进行链接,生成可执行程序,记录文件的大小。
(1)先创建一个目录,保存文件。
mkdir test3
(2)输入命令进入创建目录。
cd test3
(3)用 vim文本编辑器编辑生成所需要的文件。
创建并编辑第一个文件命令如下:
vim sub1.c
具体代码:
#include <stdio.h>
float x2x(int a,int b)
{
float c;
c=a*b;
return c;
}
创建并编辑第二个文件命令如下:
vim sub2.c
具体代码:
#include <stdio.h>
float x2y(int a,int b)
{
float c;
c=a/b;
return c;
}
创建并编辑第三个文件命令如下:
vim main.h
具体代码:
#ifndef MAIN_H
#define MAIN_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif
创建并编辑第四个文件命令如下:
vim main.c
具体代码:
#include <stdlib.h>
#include "main.h"
void main()
{
int a,b;
float c;
printf("Please enter number:");
scanf("%d%d",&a,&b);
printf("The product of a and b is:%f\n",x2x(a,b));
printf("A over b is equal to:%f\n",x2y(a,b));
}
(4)生成目标文件(xxx.o)。
gcc -c sub1.c sub2.c
(5)将x2x、x2y目标文件生成静态库.a 文件。
ar crv libsub.a sub1.o sub2.o
(6)使用.a 库文件,创建可执行程序。
gcc -o main main.c libsub.a
(7)运行文件。
./main
(8)查看静态库生成的文件大小。
ls -la
可以看到如下结果:
5、目标文件与此动态库文件进行链接,生成可执行程序,记录文件的大小。
(1)由.o 文件创建动态库文件。
gcc -shared -fPIC -o libsub.so sub1.o sub2.o
(2)使用.so 库文件,创建可执行程序。
gcc -o main3 main.c libsub.so
(3)将对应 .so 文件拷贝到对应路径。
sudo cp libsub.so /usr/lib
(4)运行程序。
./main3
(5)查看动态库生成的文件大小。
ls -la
可以看到如下结果:
可以观察到静态库中.a文件小于动态库中.so文件。
四、Linux中GCC常用命令和GCC编译器背后的故事
1、简介
GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C 语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL 语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语 言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另 一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。
2、Binutils
一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、 ldd、readelf、 size 等。
(1) addr2line:用 来将程序 地址转 换成其所 对应的程 序源文 件及所对 应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
(2) as:主要用于汇编。
(3) ld:主要用于链接。
(4) ar:主要用于创建静态库。
(5) ldd:可以用于查看一个可执行程序依赖的共享库。
(6) objcopy:将一种对象文件翻译成另一种格式,譬如将.bin 转换成.elf、或 者将.elf 转换成.bin 等。
(7) objdump:主要的作用是反汇编。
(8) readelf:显示有关 ELF 文件的信息。
(9) size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等。
3、简单编译
(1)先创建一个目录,保存文件。
mkdir test4
(2)输入命令进入创建目录。
cd test4
(3)用 vim文本编辑器编辑生成所需要的文件。
创建并编辑文件命令如下:
vim test.c
具体代码:
#include <stdio.h>
int main(void)
{
printf("Hello World!\n");
return 0;
}
(4)预处理。
gcc -E test.c -o test.i 或 gcc -E test.c
(5)编译为汇编代码(Compilation)。
gcc -S test.i -o test.s
(6)汇编(Assembly)。
gcc -c test.s -o test.o
(7)连接(Linking)。
gcc test.o -o test
(8)运行程序。
./test
结果如下图所示:
五、as汇编编译器
1、在ubuntu中安装nasm
(1)简介:as汇编编译器针对的是AT&T汇编代码风格,Intel风格的汇编代码则可以用nasm汇编编译器编译生成执行程序。
(2)下载安装nasm。
sudo apt install nasm
具体如下图所示:
(2)创建一个目录,保存文件。
mkdir test5
(3)输入命令进入创建目录。
cd test5
(3)用 vim文本编辑器编辑生成所需要的文件。
创建并编辑文件命令如下:
vim 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 ; 调用内核功能
(4)生成一个hello.o文件。
nasm -f elf64 hello.asm
(5)生成一个可执行文件hello。
ld -s -o hello hello.o
(6)运行程序
./hello
具体结果如下所示:
(7)创建helloworld.c程序。
vim helloword.c
具体代码:
#include<stdio.h>
void main()
{
printf("Hello,world");
}
(8)运行程序。
gcc helloworld.c
./a.out
结果如下图所示:
(9)比较nasm与c生成的程序大小。
size hello
size a.out
结果如下图所示:
六、Linux下用第三方库函数进行代码设计
1、Linux 系统中终端程序最常用的光标库
curses函数库能够优化光标的移动并最小化需要对屏幕进行的刷新,从而也减少了必须向字符终端发送的字符数目。
2、基本函数名称及功能
移动光标:
int move(int new_y, int new_x);
int leaveok(WINDOW *window_ptr,bool leave_flag);
屏幕读取:
chtype inch(void);
int instr(char *string);
int innstr(char *string, int numbers);
清除屏幕:
int erase(void);
int clear(void);
int clrtobot(void);
窗口优化屏幕刷新:
int wnoutrefresh(WINDOW *window_ptr);
int doupdate(void);
.
.
.
3、体验BBS
(1)进入控制面板找到程序。
(2)找到并点击启用或关闭Windows功能。
(3)启用 “telnet client” 和"适用于Linux的Windows子系统"。
(4)打开一个cmd命令行窗口。
(5)命令行输入 telnet bbs.newsmth.net,如果感兴趣,开始体验吧!
4、安装curses库
输入命令,安装curses。
sudo apt-get install libncurses5-dev
具体结果如下:
5、Linux 环境下gcc编译实现小游戏
参考curses库实现的弹球游戏
创建一个文件,将上面大佬的程序复制进去,进行编译,生成可执行文件后运行,就可以玩弹珠游戏了哟!!!
七、总结
本次练习,对Linux的操作命令有一定要求,需要比较熟悉指令操作。在练习过程中,会出现各种各样的问题,cdsn上有很多大佬的博客有解决办法,大家可以找一找,解决问题的时候是真快乐!当然也欢迎大家一起讨论哦!