一、用gcc生成.a静态库和.so动态库
1.创建一个a文件夹,然后在a文件夹中创建三个子程序a.h、a.c以及main.c
代码如下:
mkdir a
cd a
vim a.h
vim a.c
vim main.c
实例显示:
子程序a.h内容:
#ifndef A_H
#define A_H
void a(const char *name)
#endif
子程序a.c内容:
#include<stdio.h>
#include"a.h"
void a(const char *name)
{
printf("Hello %s!\n",name);
}
子程序main.c内容:
#include"a.h"
int main()
{
a("World");
return 0;
}
2.将a.c编译成为a.o文件
gcc -c a.c
ls
结果如下:
可以看到已经有a.o文件了
3.由a.o文件创建静态库,并使用
(1)创建静态库
ar -crv libmya.a a.o
ls
结果如下:
(2)在程序中使用静态库
gcc main.c libmya.a -o a
然后运行程序
./a
结果如下:
4.由a.o文件创建动态库,并在程序中使用
(1)a.o文件创建动态库
gcc -shared -fPIC -o libmya.so a.o
ls
结果如下:
(2)在程序中使用动态库
gcc main.c libmya.so -o a
结果如下:
运行时报错,因为程序在运行时会在/usr/lib和/lib等目录中找不到需要的动态库文件。
解决方法:将libmya.so文件移动至目录/usr/lib中就行了。
程序如下:
sudo mv libmya.so /usr/lib
然后运行结果如下:
二、动态库和静态库生成可执行文件大小的对比
1.创建另外一个文件夹b,然后在文件夹b中分别创建子程序sub1.h、sub1.c、sub2.h、sub2.c、main.c
mkdir b
cd b
vim sub1.h
vim sub1.c
vim sub2.h
vim sub2.c
vim main.c
sub1.h内容如下:
#ifndef SUB1_H
#difine SUB1_H
float x2x(int a,int b);
#endif
sub1.c内容如下:
#include"sub1.h"
float x2x(int a,int b)
{
return a+b;
}
sub2.h内容如下:
#ifndef SUB2_H
#difine SUB2_H
float x2y(int a,int b);
#endif
sub2.c内容如下:
#include"sub2.h"
float x2y(int a,int b)
{
return a*b;
}
main.c内容如下:
#include<stdio.h>
#include"sub1.h"
#include"sub2.h"
int main()
{
int a=4,b=5;
printf("%d + %d =%f\n",a,b,x2x(a,b));
printf("%d * %d =%f\n",a,b,x2y(a,b));
}
2.用静态库文件进行链接,生成可执行文件
(1)将sub1.c、sub2.c文件分别编译成sub1.o、sub2.o文件
gcc -c sub1.c sub2.c
ls
结果如下:
(2)创建静态库
ar -crv libsub1.a sub1.o
ar -crv libsub2.a sub2.o
ls
(3)在程序中使用静态库
gcc main.c libsub1.a libsub2.a -o main1
./main1
结果如下:
3.用动态库文件进行链接,生成可执行文件
(1)创建动态库
gcc -shared -fPIC -o libsub1.so sub1.o
gcc -shared -fPIC -o libsub2.so sub2.o
ls
结果如下:
(2)使用动态库
gcc main.c libsub1.so libsub2.so -o main2
sudo mv libsub1.so /usr/lib
sudo mv libsub2.so /usr/lib
./main2
结果如下:
4.两个可执行文件大小的比较
gcc -static main.c libsub1.a libsub2.a -o main1
size main1
ldd main1
size main2
ldd main2
结果如下:
三、gcc编译器是怎么编译的
1.创建一个test文件夹,然后在test文件夹中创建一个a.c程序
mkdir test
cd test
vim a.c
a.c内容如下:
#include <stdio.h>
int main(void)
{
printf("Hello World! \n");
return 0;
}
2.编译过程
(1)预编译(将源文件 a.c 文件预处理生成 a.i)
gcc -E a.c -o a.i
(2)编译(将预处理生成的 a.i 文件编译生成汇编程序 a.s)
gcc -S a.i -o a.s
(3)汇编(将编译生成的 a.s 文件汇编生成目标文件 a.o)
gcc -c a.s -o a.o
(4)链接(分为静态链接和动态链接,生成可执行文件)
gcc a.c -o a //动态链接
gcc -static a.c -o a //静态链接
结果如下:
3.ELF文件的分析
(一)一个典型的 ELF 文件包含下面几个段
(1) .text:已编译程序的指令代码段
(2) .rodata:ro 代表 read only,即只读数据(譬如常数 const)
(3) .data:已初始化的 C 程序全局变量和静态局部变量
(4) .bss:未初始化的 C 程序全局变量和静态局部变量
(5) .debug:调试符号表,调试器用此段的信息帮助调试
readelf -S a //查看各个section的信息
如下:
(二)反汇编ELF
objdump -S 将其反汇编并且将其 C 语言源代码混合显示出来:
gcc -o a -g a.c
objdump -S a
显示如下:
4.在Ubuntu中下载安装nasm,对示例汇编代码“hello.asm”编译生成可执行程序,并与上述用C代码的编译生成的可执行程序大小进行对比
4.1安装nams
输入nasm
nasm
显示如下:
说明Ubuntu中没有安装nasm
说明Ubuntu中已经安装了nasm
如果没有安装nasm,输入代码sudo apt install nasm
sudo apt install nasm
结果如下:
4.2编译汇编 hello.asm文件
(1)创建hello.asm文件
vim 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 ; 调用内核功能
(2)编译、链接
nasm -f elf64 hello.asm
ld -s -o hello hello.o
./hello
结果如下:
4.3汇编与C代码的编译生成的可执行程序大小对比
汇编生成的可执行程序大小
c代码生成的可执行程序大小
四、了解实际程序是如何借助第三方库函数完成代码设计
1.光标库(curses)的常见函数功能
nitscr() 初始化curses库和ttty
endwin() 关闭curses并重置tty
move(y,x) 将光标移动至x,y的位置
getyx(win,y,x) 得到目标游标的位置
clear()and erase() 将整个屏幕清楚
2.体验一下即将绝迹的远古时代的 BBS
在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和 “适用于Linux的Windows子系统”(后面会使用),然后重启。
3.Linux 环境下C语言编译实现贪吃蛇游戏
(1)安装curses库
sudo apt-get install libncurses5-dev
通过 whereis 命令头文件和库文件都被安装到哪些目录中
whereis curses.h
(2)编译实现贪吃蛇游戏
代码参考
Linux 环境下C语言编译实现贪吃蛇游戏
创建Snake文件,然后在Snake文件中创建snake.c程序
mkdir Snake
cd Snake
vim snake.c