一、可执行程序的组装
1、用gcc生成静态库和动态库的实例练习与静态库.a与.so库文件的生成与使用的实例练习
1)、用gcc生成静态库和动态库的实例练习
- 用gcc生成.a静态库
步骤一:写入源代码hello.h、hello.c、main.c分别如下
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;
}
步骤二:将 hello.c 编译成.o 文件
输入命令行gcc -c hello.c
步骤三:由.o 文件创建静态库
输入命令行ar -crv libmyhello.a hello.o
步骤四:在程序中使用静态库
建立可执行程序
输入命令行gcc main.c libmyhello.a -o hello
运行可执行程序hello
输入命令行./hello
步骤五:删除libmyhello.a后hello是否还可执行
结果发现:程序照常运行,静态库中的公用函数已经连接到目标文件中了。
-
用gcc生成.so动态库
步骤一:运用之前的源代码由.o 文件创建动态库文件输入命令行gcc -shared -fPIC -o libmyhello.so hello.c
步骤二:在程序中使用动态库
输入命令行gcc -o hello1 main.c -L. -lmyhello形成hello1可执行程序
输入命令行mv libmyhello.so /usr/lib
再输入命令行./hello1
当我们未将文件 libmyhello.so 复制到目录/usr/lib 中时。程序在运行时, 不能在/usr/lib 和/lib 等目录中查找需要的动态库文件。会出现错误。
-
当静态库和动态库同时存在时,优先权的问题
步骤一:创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so
步骤二:运行 gcc 命令来使用函数库 myhello 生成目 标文件 hello
步骤三:运行程序 hello
从程序 hello 运行的结果中很容易知道,当静态库和动态库同名时,gcc 命令将优先使用动 态库,默认去连/usr/lib 和/lib 等目录中的动态库,将文件 libmyhello.so 复制到目录/usr/lib 中即可。
2)、静态库.a与.so库文件的生成与使用的实例练习
-
用gcc生成.a静态库
步骤一:写入源代码A.h、A1.c、A2.c、test.c分别如下
A.h
#ifndef A_H #define A_H void print1(int); void print2(char *); #endif
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); }
test.c
#include <stdlib.h> #include "A.h" int main(){ print1(1); print2("test"); exit(0); }
步骤二:将 A1.c 、A2.c编译成.o 文件
输入命令行gcc -c A1.c A2.c
步骤三:生成静态库.a 文件
输入命令行ar crv libafile.a A1.o A2.o
步骤四:在程序中使用静态库
输入命令行gcc -o test test.c libafile.a
再输入命令行./test
-
用gcc生成.so动态库
步骤一:运用之前的源代码由.o 文件创建动态库文件
输入命令行gcc -c -fpic A1.c A2.c
gcc -shared *.o -o libsofile.so
步骤二:在程序中使用动态库
输入命令行gcc -o test test.c libsofile.so
再输入命令行mv libmyhello.so /usr/lib与./test
注意事项同上
2、对第一次作业进行改编,进行静态库与动态库的生成
1)、 用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小
步骤一:写入源代码sub1.h、sub1.c、main.c分别如下
sub1.c
#include<stdio.h>
int x2x(int a,int b){ //定义x2x函数供main1.c使用
int c;
c=a+b;
return c;
}
int x2y(int a,int b){
int c;
c=a*b;
return c;
}
sub1.h
#ifndef SUB1_H
#define SUB1_H
int x2x(int,int);
int x2y(int,int);
#endif
main.c
include<stdio.h>
#include "sub1.h" //从sub1.c中引入x2x函数
int main(){
int a,b;
int x,y;
scanf("%d%d",&a,&b);
x=x2x(a,b);
printf("a+b=%d\n",x);
y=x2y(a,b);
printf("a*b=%d\n",y);
return 0;
}
步骤二:将 sub1编译成.o 文件
输入命令行gcc -c sub1.c
步骤三:生成静态库.a 文件
输入命令行ar -crv libasub.a sub1.o
步骤四:在程序中使用静态库
输入命令行gcc -o main main.c libasub.a
再输入命令行./main
2)、用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小
步骤一:运用之前的源代码由.o 文件创建动态库文件
输入命令行gcc -shared -fPIC -o libsosub.so sub1.c
步骤二:在程序中使用动态库
输入命令行gcc -o main1 main.c -L. -lsosub
再输入命令行mv libsosub.so /usr/lib与./main1
注意事项同上
3)、静态库下的可执行文件与动态库下的可执行文件大小的比较
由下图:
可见,动态库下的可执行文件的大小要大于静态库下的可执行文件的大小。
二、gcc编译工具集中各软件的用途
1、Linux 常用命令GCC的简单编译功能
用gcc进行编译可一步到位的编译,但实际上的编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译 (Compilation)、汇编 (Assembly)和连接(Linking)。
下面我们创建两个测试文件,test.c与test1.c,两个文件的内容一致,均为:
#include<stdio.h>
int main(){
printf("hello world!\n");
return 0;
}
通过这两个文件我们来看gcc一步到位的编译与实际上的编译过程。
-
一步到位的编译
直接输入命令行gcc test.c -o test
输入命令行./test
-
实际上的编译过程
步骤一:预处理
输入命令行gcc -E test1.c -o test1.i
发现目录下生成了test1.i文件。
步骤二:编译为汇编代码
输入命令行gcc -S test1.i -o test1.s
发现目录下生成了test1.s文件。
步骤三:汇编
输入命令行gcc -c test1.s -o test1.o
发现目录下生成了test1.o文件。
步骤四:连接
输入命令行gcc test1.o -o test1
发现目录下生成了test1可执行文件。
输入命令行./test1
2、分析 ELF 文件
-
ELF 文件的段
输入命令行readelf -S test
-
反汇编 ELF
输入命令行objdump -D hello
-
使用 objdump -S 将其反汇编并且将其 C 语言源代码混合显示
输入命令行gcc -o hello -g hello.c
objdump -S hello
3、用nasm汇编编译器编译生成执行程序
准备工作:下载所给hello.asm
步骤一:形成目标文件hello.o
输入命令行nasm -f elf32 hello.asm hello.o
发现目录下已生成hello.o文件
步骤二:形成可执行文件hello
输入命令行ld -m elf_i386 hello.o -o hello
发现目录下已生成hello文件
步骤三:执行可执行文件hello
输入命令行./hello
步骤四:比较此可执行文件hello与普通hello world程序可执行文件hello1的文件大小
可见汇编编译器下形成的hello很小,远小于gcc编译器下形成的hello。
三、借助第三方库函数完成代码设计
1、光标库(curses)的主要函数功能
- initscr(): initscr() 是一般 curses 程式必须先呼叫的函数, 一但这个函数被呼叫之後, 系统将根据终端机的形态并启动 curses 模式.
- endwin(): curses 通常以呼叫 endwin() 来结束程式. endwin() 可用来关闭curses 模式, 或是暂时的跳离 curses 模式. 如果您在程式中须要call shell ( 如呼叫 system() 函式 ) 或是需要做 system call, 就必须先以 endwin() 暂时跳离 curses 模式. 最後再以wrefresh() doupdate() 来重返 curses 模式.
- cbreak() and nocbreak(): 当 cbreak 模式被开启後, 除了 DELETE 或 CTRL 等仍被视为特殊控制字元外一切输入的字元将立刻被一一读取.当处於 nocbreak 模式时, 从键盘输入的字元将被储存在 buffer 里直到输入 RETURN或 NEWLINE.在较旧版的 curses 须呼叫 crmode(),nocrmode() 来取代 cbreak(),nocbreak()
- nl() and nonl(): 用来决定当输入资料时, 按下 RETURN 键是否被对应为 NEWLINE 字元 ( 如 /n ). 而输出资料时, NEWLINE 字元是否被对应为 RETURN 和 LINDFEED系统预设是开启的.
- echo() and noecho(): 此函式用来控制从键盘输入字元时是否将字元显示在终端机上.系统预设是开启的.
- intrflush(win,bf): 呼叫 intrflush 时须传入两个值, win 为一 WINDOW 型态指标, 通常传入标准输出入萤幕 stdscr. bf 为 TRUE 或 FALSE. 当 bf 为 true 时, 当输入中断字元 ( 如 break) 时, 中断的反应将较为快速.但可能会造成萤幕的错乱.
- keypad(win,bf): 呼叫 keypad 时须传入两个值, win 为一 WINDOW 型态指标, 通常传入标准输出入萤幕 stdscr. bf 为 TRUE 或 FALSE. 当开启 keypad 後, 可以使用键盘上的一些特殊字元, 如上下左右>等方向键, curses 会将这些特殊字元转换成 curses.h 内定义的一些特殊键. 这些定义的特殊键通常以 KEY_ 开头.
- refresh(): refresh() 为 curses 最常呼叫的一个函式. curses 为了使萤幕输出入达最佳化, 当您呼叫萤幕输出函式企图改变萤幕上的画面时, curses 并不会立刻对萤幕做改变, 而是等到refresh() 呼叫後, 才将刚才所做的变动一次完成. 其馀的资料将维持不变. 以尽可能送最少的字元至萤幕上. 减少萤幕重绘的时间.如果是 initscr() 後第一次呼叫 refresh(), curses 将做清除萤幕的工作.
2、以游客身份体验一下即将绝迹的远古时代的 BBS
步骤一:在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”
步骤二:启用 “telnet client” 和"适用于Linux的Windows子系统"
步骤三:以游客身份登录
进入后的界面:
3、安装curses库
输入命令行sudo apt-get install libncurses5-dev进行安装
发现头文件curse.h在目录/usr/include下
库文件在/usr/lib下。
4、Linux 环境下C语言编译实现贪吃蛇游戏
贪吃蛇源代码链接:http://www.linuxidc.com/Linux/2011-08/41375.htm
输入命令行cc mysnake1.0.c -lcurses -o mysnake1.0
输入命令行./mysnake1执行程序
效果如下: