Ubuntu深入学习
一、可执行程序是如何被组装的?
1.编辑:也就是编写C/C++程序。
2.预处理:相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有宏定义,没有条件编译指令,没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。
3.编译:将预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。
4.链接:通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。在此过程中会发现被调用的函数未被定义。
二、用gcc生成静态库和动态库
1.编辑生成例子程序
先创建一个作业目录,保存本次练习的文件
mkdir test1
cd test1
这里使用vi命令创建并编辑3个所需文件:
程序 1: hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
程序 2: hello.c
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
程序 3: main.c
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
2.将hello.c编译成.o文件
无论静态库,还是动态库,都是由.o 文件创建的。因此,我们必须将源程序 hello.c 通过 gcc 先编译成.o 文件。
在系统提示符下键入以下命令得到 hello.o 文件。
gcc -c hello.c
然后用ls命令查看是否生成了hello.o文件
3.由.o 文件创建静态库文件
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。
例如:我们将创建的静态库名为 myhello,则静态库文件名就libmyhello.a。在创建和使用静态库时,
需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文件libmyhello.a。
ar -crv libmyhello.a hello.o
然后用ls命令查看结果:
ls 命令结果中有 libmyhello.a
4.在程序中使用静态库
静态库制作完了,如何使用它内部的函数呢?
只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。
注意,gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a 得到的静态库文件名来查找静态库文件。
#gcc main.c libmyhello.a -o hello
运行结果如下
我们删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中了
# rm libmyhello.a
rm: remove regular file `libmyhello.a'? y
# ./hello
Hello everyone!
#
5.由.o 文件创建动态库文件
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyhello.so。
用 gcc 来创建动态库。
gcc -shared -fPIC -o libmyhello.so hello.o
依然使用ls命令查看是否生成动态库文件libmyhello.so
6.在程序中使用动态库
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。
我们先运行 gcc 命令生成目标文件,再运行它看看结果。
gcc -o hello main.c -L. -lmyhello
# ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
运行结果如下
程序在运行时,会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录/usr/lib 中,再试试
mv libmyhello.so /usr/lib
./hello
Hello everyone!
这里我们需要在命令行前加入sudo,提供我们所需要的权限,否则无法进行文件的复制
成功了。这也进一步说明了动态库在程序运行时是需要的。
我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的 gcc 命令完全一样,那当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?抱着对问题必究到底的心情,来试试看。
先删除除.c 和.h 外的所有文件,恢复成我们刚刚编辑完举例程序状态。
rm -f hello hello.o /usr/lib/libmyhello.so
ls
hello.c hello.h main.c
这里同样使用了sudo,运行结果如下
再来创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so。
# gcc -c hello.c
# ar -cr libmyhello.a hello.o (或-cvr )
# gcc -shared -fPIC -o libmyhello.so hello.o
# ls
hello.c hello.h hello.o libmyhello.a libmyhello.so main.c
运行结果如下
通过上述最后一条 ls 命令,可以发现静态库文件 libmyhello.a 和动态库文件 libmyhello.so 都已经生成,并都在当前目录中。然后,我们运行 gcc 命令来使用函数库 myhello 生成目标文件 hello,并运行程序 hello。
# gcc -o hello main.c -L. –lmyhello
# ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
#
三、静态库应用
静态库应用内容:除了x2x函数之外,再扩展写一个x2y函数(功能自定),main函数代码将调用x2x和x2y ;将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件;将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小。
1.建立文件并写入代码
#gedit sub1.c
#gedit sub2.c
#gedit main.c
子函数sub1.c代码如下
float x2x(float a, float b) {
return a * b;
}
子函数sub2.c代码如下
float x2y(float a, float b) {
return a + b;
}
主函数main.c代码如下
#include<stdio.h>
extern float x2x(float a, float b);
extern float x2y(float a, float b);
int main() {
float a = 2;
float b = 3;
printf("ax2xb=%f\n",x2x(a,b));
printf("ax2yb=%f\n",x2y(a,b));
}
2.编译.o文件并建立静态库文件链接
#gcc -c sub1.c
#gcc -c sub2.c
#gcc -c main.c
ls
运行结果如下
由ar命令建立静态库
#ar -crv 1.a sub1.o sub2.o
调用sub1可执行程序结果如下
文件大小如下
四、动态库应用
动态库应用内容:将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。
1.建立文件并写入代码
文件与代码与(三)静态库应用的内容一致,此处我们将.a和可执行程序删除后,进行动态库文件的生成和应用
#rm 1.a
#rm sub1
ls
2.建立动态库文件链接
由.o文件,用gcc创建动态库文件
#gcc -shared -fPIC -o 2.so sub1.o sub2.o
运行结果如下:
主函数与动态库文件进行链接成可执行文件
运行结果如下
文件大小如下
总结:动态库文件比静态库文件所占内存要小
五、Linux GCC常用命令
1.创建示例程序
//test.c
#include <stdio.h>
int main(void)
{
printf("Hello World!\n");
return 0;
}
2.预处理
gcc -E test.c -o test.i 或 gcc -E test.c
可以输出 test.i 文件中存放着 test.c 经预处理之后的代码。打开 test.i 文件,看一看,就明白了。后面那条指令,是直接在命令行窗口中输出预处理后的代码. gcc 的-E 选项,可以让编译器在预处理后停止,并输出预处理结果。在本例中,预处理结果就是将stdio.h 文件中的内容插入到 test.c 中了。
运行结果如下
3.编译为汇编代码
预处理之后,可直接对生成的 test.i 文件编译,生成汇编代码:
gcc -S test.i -o test.s
gcc 的-S 选项,表示在程序编译期间,在生成汇编代码后,停止,-o 输出汇编代码文件。
4.汇编
对于上一小节中生成的汇编代码文件 test.s,gas 汇编器负责将其编译为目标文件,如下
gcc -c test.s -o test.o
5.连接
gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。对于上一小节中生成的 test.o,将其与C标准输入输出库进行连接,最终生成程序 test
gcc test.o -o test
运行结果如下
6.多个程序文件的编译
通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用 GCC 能够很好地管理
这些编译单元。假设有一个由 test1.c 和 test2.c 两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序 test,可以使用下面这条命令:
gcc test1.c test2.c -o test
如果同时处理的文件不止一个,GCC 仍然会按照预处理、编译和链接的过程依次进行。如果深究起
来,上面这条命令大致相当于依次执行如下三条命令:
gcc -c test1.c -o test1.o
gcc -c test2.c -o test2.o
gcc test1.o test2.o -o test
7.检错
gcc -pedantic illcode.c -o illcode
-pedantic 编译选项并不能保证被编译程序与 ANSI/ISO C 标准的完全兼容,它仅仅只能用来帮助Linux 程序员离这个目标越来越近。或者换句话说,pedantic 选项能够帮助程序员发现一些不符合ANSI/ISO C 标准的代码,但不是全部,事实上只有 ANSI/ISO C 语言标准中要求进行编译器诊断的那些情况,才有可能被 GCC 发现并提出警告。
除了-pedantic 之外,GCC 还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall 了,使用它能够使 GCC 产生尽可能多的警告信息。
gcc -Wall illcode.c -o illcode
GCC 给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。一个优秀的 Linux 程序员应该尽量避免产生警告信息,使自己的代码始终保持标准、健壮的特性。所以将警告信息当成编码错误来对待,是一种值得赞扬的行为!所以,在编译程序时带上-Werror 选项,那么 GCC 会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:
gcc -Werror test.c -o test
运行结果
8.库文件连接
编译成可执行文件
首先我们要进行编译 test.c 为目标文件,这个时候需要执行
gcc –c –I /usr/dev/mysql/include test.c –o test.o
链接
最后我们把所有目标文件链接成可执行文件:
gcc –L /usr/dev/mysql/lib –lmysqlclient test.o –o test
Linux 下的库文件分为两大类分别是动态链接库(通常以.so 结尾)和静态链接库(通常以.a 结尾),
二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。
强制链接时使用静态链接库
默认情况下, GCC 在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链
接库,如果需要的话可以在编译时加上-static 选项,强制使用静态链接库。
在/usr/dev/mysql/lib 目录下有链接时所需要的库文件 libmysqlclient.so 和 libmysqlclient.a,为了让
GCC 在链接时只用到静态链接库,可以使用下面的命令:
gcc –L /usr/dev/mysql/lib –static –lmysqlclient test.o –o test
静态库链接时搜索路径顺序:
- ld 会去找 GCC 命令中的参数-L
- 再找 gcc 的环境变量 LIBRARY_PATH
- 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初 compile gcc 时写在程序内的
动态链接时、执行时搜索路径顺序: - 编译目标代码时指定的动态库搜索路径
- 环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径
- 配置文件/etc/ld.so.conf 中指定的动态库搜索路径
- 默认的动态库搜索路径/lib
- 默认的动态库搜索路径/usr/lib
六.nasm应用
1.安装nasm
sudo apt-get install nasm//安装nasm
nasm -version//查看版本
2.nasm使用
对示例代码“hello.asm”编译生成可执行程序,并与“hello world”C代码的编译生成的程序大小进行对比。
所需代码:
编译
nasm -f elf64 hello.asm
链接
ld -s -o hello hello.o
运行
./hello
运行结果如下
生成文件的大小
此处为“hello world”的C代码,经过gcc编译生成的可执行程序的文件大小
总结:nasm的文件比gcc生成的文件要小
七.借助第三方库函数完成代码设计
1.光标库 curses
函数名 | 作用 |
---|---|
initscr() | 初始化curses库和tty |
endwin() | 关闭curses并重置tty |
refresh() | 使屏幕按照你的意图显示 |
move(r,c) | move(r,c) |
addstr(s) | 在当前位置画字符串s |
addch(c) | 在当前位置画字符c |
clear() | 清屏 |
参考资料:
https://blog.csdn.net/akof1314/article/details/5948578
2.体验即将绝迹的远古时代的 BBS
3.curses库应用
安装curses库
sudo apt-get install libncurses5-dev
安装过程遇到该错误
E: Unable to lock the administration directory (/var/lib/dpkg/),
is another process using it?
参考资料得以解决
https://blog.csdn.net/pfanaya/article/details/6695810
其头文件被安装在/usr/include
4.贪吃蛇游戏
建立可执行文件代码如下
游戏如图所示:
八.总结
学习过程艰难,花费时间较多,收获良多,下次继续努力QAQ