目录
一、可执行程序的组装
1.1 用gcc生成.a静态库和.so动态库(test1)
(1)编辑生成例子程序 hello.h、hello.c 和 main.c
创建作业目录:
mkdir test1 //创建test1文件夹
ls //显示文件及目录
cd test1 //进入目录test1
用 vim创建hello.h、hello.c 和 main.c3 个文件。
hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
hello.c
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
main.c
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
(2)将 hello.c 编译成.o 文件
gcc -c hello.c
ls //运行ls命令查看是否产生hello.o
(3)由.o文件创建静态库
ar -crv libmyhello.a hello.o //创建静态库
ls //运行ls命令查看结果
(4)在程序中使用静态库
在 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中
gcc main.c libmyhello.a -o hello
静态库中的函数已经连接到目标文件中,在执行文件时,就已经不需要静态库以及hello.c与hello.h
用方法一时,过程中出现以上问题,原因是指导书的“-lmyhello”,“-”是中文符号。
(5) 由.o 文件创建动态库文件
# gcc -shared -fPIC -o libmyhello.so hello.o //创建静态库
ls //运行ls命令查看结果
错误:
/usr/bin/ld: hello.o: relocation R_X86_64_32 against `.rodata’ can not be used when making a shared object; recompile with -fPIC
hello.o: 无法添加符号: 错误的值
解决方法:
(1)重新编译 hello.o
gcc -c -fPIC hello.c
然后用这个hello.o生成动态库就不会报错
(2)直接将代码改为
gcc -shared -fPIC -o libmyhello.so hello.c
(也是从.c文件重新编译)
(6)在程序中使用动态库;
和静态库一样,用 gcc 命令生成目标文件时指明动态库名,进行编译,就可以生成目标文件
gcc main.c libmyhello.so -o hello
./hello
错误提示:找不到动态库文件 libmyhello.so。
程序在运行时,会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。
而我的动态库文件在text1里,所以报错
将文件 libmyhello.so 复制到目录/usr/lib 中——>权限不够——>登录管理员模式——>移动成功,hello程序执行成功
(7)静态库和动态库同名
比较当静态库和动态库同名时,gcc 命令会使用哪个库文件
按照以上步骤,重新创建静态库(.a)和动态库(.so)文件(.so文件不用移目录)
运行hello程序后,出现错误,说明gcc先使用动态库
(8)小结
**静态库:**在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
**动态库:**在程序编译时并不会被连接到目标代码中,在程序运行是才被载入
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,文件扩展名为.so
-fPIC: 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。
-shared: 生成共享目标文件。通常用在建立共享库时
**ar:**主要用于创建静态库
1.2 用gcc生成.a静态库和.so动态库(test2)
编辑作业程序 a1.c 、a2.c、a.h和test.c
创建作业目录:
mkdir test2 //创建test1文件夹
cd test2 //进入目录test1
用 vim创建 a1.c 、a2.c、a.h和test.c 4个文件
a1.c
#include <stdio.h>
void print1(int arg){
printf("A1 print arg:%d\n",arg);
}
a2.c
#include <stdio.h>
void print2(char *arg){
printf("A2 printf arg:%s\n", arg);
}
a.h
#ifndef A_H
#define A_H
void print1(int);
void print2(char *);
#endif
test2.c
#include <stdlib.h>
#include "A.h"
int main(){
print1(1);
print2("test");
exit(0);
}
(1)静态库.a 文件的生成与使用
1.1 生成目标文件a1.o、a2.o
gcc -c a1.c a2.c //用gcc生成.o文件
ls //查看
1.2 生成静态库文件liba.a
ar crv liba.a a1.o a2.o //由a1.o和a2.o生成静态库文件
ls //查看
1.3 使用.a 库文件,创建可执行程序
gcc -o test test.c libafile.a
./test
出现以下错误:
原因是我把文件名改为小写的,只需修改test.c相应部分即可
(2)共享库.so 文件的生成与使用
2.1 生成目标文件a1.o / a2.o( 此处生成.o 文件必须添加"-fpic")
gcc -c -fpic a1.c a2.c
2.2 生成共享库.so 文件
gcc -shared *.o -o libso.so
2.3 使用.so 库文件,创建可执行程序
gcc -o test test.c libso.so
./test
会出现和上题同样的错误,解决方法一样
2.1 在作业1的基础上建立静态库
(1)编辑生成例子程序 b.h、b1.c、b2 和 main.c
b1.c(a和b相加)
#include <stdio.h>
float x2x(int a,int b)
{
return a+b;
}
b2.c(a和b相乘)
#include <stdio.h>
float x2y(int a,int b)
{
return a*b;
}
b.h
#ifndef b_H
#define b_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif
main.c
#include <stdlib.h>
#include "b.h"
int main()
{
int a=0,b=0;
scanf("%d%d",&a,&b);
printf("%d+%d=%f/n",a,b,x2x(a,b));
printf("%d+%d=%f/n",a,b,x2y(a,b));
exit(0);
}
(2)将 b1.c/b2.c 编译成.o 文件
gcc -c b1.c b2.c
(3)生成静态库文件libb.a
ar crv libb.a b1.o b2.o //由a1.o和a2.o生成静态库文件
ls //查看
(4)使用.a 库文件,创建可执行程序
gcc -o main main.c libb.a
./test
(5)静态库文件大小
3.1 在作业1的基础上建立静态库
(1)编辑生成例子程序 b.h、b1.c、b2 和 main.c(2.1已完成)
(2)将 b1.c/b2.c 编译成.o 文件( 此处生成.o 文件必须添加"-fpic")
gcc -c -fpic b1.c b2.c
(3)生成静态库文件libb.so
ar crv libb.so b1.o b2.o //由a1.o和a2.o生成静态库文件
ls //查看
(4)使用.a 库文件,创建可执行程序
gcc -o main main.c libb.so
mv libb.so /usr/libsi
./main
(5)动态库文件大小
我的程序,动态库与静态库,可执行文件大小一样,libb.so libb.a大小也相同(可能是程序过于简单)
二、gcc编译工具中各软件的用途
1.1 Linux GCC常用命令
(1)简单的编译过程
1.1直接生成可执行文件
(以下的test我都是用的zxx)
#include <stdio.h>
int main(void)
{
printf("Hello World!\n");
return 0;
}
gcc zxx.c -o test
述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(Linking)
1.2 预编译(Preprocessing)
gcc -E test.c -o test.i //生成预处理文件,test.i
gcc -E test.c //将预处理文件显示在命令行中
第一种:
第二种:
生成的是一段很长的看不懂的代码;
这就是预处理后的代码
gcc 的-E 选项,可以让编译器在预处理后停止,并输出预处理结果。
1.3 编译–编码为汇编语言(Compilation)
预处理之后,可直接对生成的 test.i 文件编译,生成汇编代码:
gcc -S test.i -o test.s
gcc 的-S 选项,表示在程序编译期间,在生成汇编代码后,停止,-o 输出汇编代码文件。
1.4 汇编(Assembly)
对于上一题中生成的汇编代码文件 test.s,
gas 汇编器负责将其编译为目标文件,如下:
gcc -c test.s -o test.o
1.5 连接(Linking)
gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生
成可执行文件。附加的目标文件包括静态连接库和动态连接库。
对于上一小节中生成的 test.o,将其与C标准输入输出库进行连接,最终生成程序 test
gcc test.o -o test
(test是1.1直接生成,zxx是一步一步生成,源程序没有差别)
(2)多个程序文件的编译
gcc可以同时编译多个源文件
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
(3)检错
常用的gcc检错命令有以下两种:
gcc -pedantic illcode.c -o illcode
gcc -Wall illcode.c -o illcode
在编译程序时带上-Werror 选项,那么 GCC 会在所有产生警告的地方停止编译,如下:
gcc -Werror test.c -o test
(4)链接
有目标文件链接成可执行文件:
gcc –L /usr/dev/mysql/lib –lmysqlclient test.o –o test //这里的路径,若不写,则默认在/usr/lib
若想让GCC 在链接时只用到静态链接库,可以使用下面的命令:
gcc –L /usr/dev/mysql/lib –static –lmysqlclient test.o –o test //-static 静态
也可以直接写出要使用的库如:
gcc -o main main.c libtest.a
1.2 GCC编译器背后的故事
(1)准备工作
#include <stdio.h>
//此程序很简单,仅仅打印一个 Hello World 的字符串。
int main(void)
{
printf("Hello World! \n");
return 0;
}
(2) 编译过程
1.1已经详细的介绍了gcc的编译过程,这里不再赘叙
直接gcc hello.c -o hello
(3)分析 ELF 文件
1.ELF 文件的段
位于 ELF Header 和 Section Header Table 之间的都是段(Section)。
一个典型的 ELF 文件包含下面几个段:
.text:已编译程序的指令代码段。
.rodata:ro 代表 read only,即只读数据(譬如常数 const)。
.data:已初始化的 C 程序全局变量和静态局部变量。
.bss:未初始化的 C 程序全局变量和静态局部变量。
.debug:调试符号表,调试器用此段的信息帮助调试。
可以使用 readelf -S 查看其各个 section 的信息如下:
readelf -S hello
2.反汇编 ELF
由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包含的指令和数据,需要使用反汇编的方法。
使用 objdump -D 对其进行反汇编如下:
objdump -D hello
使用 objdump -S 将其反汇编并且将其 C 语言源代码混合显示出来:
gcc -o hello -g hello.c //要加上-g 选项
objdump -S hello
2.1 nasm的使用
NASM是一个为可移植性与模块化而设计的一个80x86的汇编器。它支持相当多的目标文件格式,包括Linux和’‘NetBSD/FreeBSD’’,’‘a.out’’,’‘ELF’’,’‘COFF’’,微软16位的’‘OBJ’‘和’‘Win32’’。
(1)安装nasm
(2)下载asm文件
在linux里打开学习通,下载hello.asm
(3)将asm编译为可执行文件
(4)查看可执行文件大小
(5)与c语言编译文件大小比较
(6)小结
nasm编译的可执行程序大小远远小于c程序。
C文件生成可执行文件,会经过预处理,编译阶段编译成汇编指令,再经过汇编阶段变成二进制文件,之后再链接阶段,形成可运行文件。
NASM只是将asm文件经过汇编阶段变成PE或ELF或COFF等二进制文件,并没有进行链接
三、 使用第三方库完成代码设计
1.1 curses的主要函数功能
转载自:Curses函数说明(SCO)
(1)字符显示
WINDOW* initscr()
SCREEN* newterm(char *type, FILE *outfd, FILE *infd)
初始化函数,对用户访问的每个终端都应该调用newterm,type是终端的名称,包括在$TERM中(如ansi, xterm, vt100等等) 。
SCREEN* set_term(SCREEN* new)
用户可以切换当前终端。所有的函数都将在设置的当前终端上起作用。
int endwin()
退出程序之前,关闭所有打开的窗口。之后还可以调用refresh()。
int delwin(win)
它删除窗口win。如果存在子窗口,以前先要删除这些子窗口。这个函数将释放win所占据的所有资源。在调用endwin()之前用户应该删除所有的窗口。
(2)输出选项
int idlok(win, bf)
void fdcok(win, bf)
这两个函数为窗口使能或者关闭终端的insert/delete特征(idlok(.)针对一行,而fdcok(.)则针对字符)。(注:idcok(.)尚未实现)
void immedok(win, bf)
bf设置为TRUE,则对窗口win的每一次改变都将导致物理屏幕的一次刷新。 这将使程序的性能降低,所以默认的值是FALSE。(注:此函数尚未实现)
int clearok(win, bf)
如果bf值为TRUE,则下一次调用wrefresh(win)时将会清除屏幕, 并完全地把它重新画一遍(就像用户在编辑器vi中按下Ctrl+L一样)。
int leaveok(win, bf)
默认的行为是,ncurses让物理光标停留在上次刷新窗口时的同一个位置上。
不使用光标的程序可以把leaveok(.)设置为TRUE,这样一般可以节省光标移动所需要的时间。
此外,ncurses将试图使终端光标不可见。
(3)输入选项
int keypad(win, bf)
bf为TRUE,函数在等待输入时会使能用户终端的键盘上的小键盘。
ncurses将返回一个键代码,该代码在.h头文件中被定义为KEY_*宏,它是针对小键盘上的功能键和方向键的。
对于PC键盘来说,这一点是非常有帮助的,因为这样用户就可以使能数字键和光标键。
int meta(win.bf)
bf为TRUE,从getch()返回的键代码将是完整的8位(最高位将不会被去掉)
int cbreak()
int nocbreak()
int crmode()
int nocrmode()
cbreak()和nocbreak()将把终端的CBREAK模式打开或关闭。
如果CBREAK打开则程序就可以立刻使用读取的输入信息。
如果CBREAK关闭,则输入将被缓存起来,直到产生新的一行
(注意:crmode()和nocrmode()只是为了提供向上兼容性,不要使用它们)
2.1 在 win10 系统中体验BBS
在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"。 然后打开一个cmd命令行窗口,命令行输telnetbbs.newsmth.net
3.1 安装curses库及头文件位置
安装代码:
sudo apt-get install libncurses5-dev
头文件在/usr/include里面
库文件在/usr/lib
4.1curses库实现的贪吃蛇游戏
贪吃蛇代码http://www.linuxidc.com/Linux/2011-08/41375.htm
vim aaa.c
gcc aaa.c -lcurses -o a
/.a