编译器背后的故事
1,创建一个 test1 文件夹,并在该文件夹中创建三个子程序 hello.h、hello.c 和 main.c
mkdir test1 # 创建test1文件夹
cd test1 # 进入该文件
vim hello.h # 编辑hello.h
vim hello.c # 编辑hello.c
vim main.c # 编辑main.c
程序 hello.h 内容如下:
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
程序 hello.c 内容如下:
#include <stdio.h>
#include "hello.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文件
,
3,由 .o文件创建静态库,并在程序中使用
1.o文件创建静态库
2. 在程序中使用静态库
gcc -o hello main.c -L. -lmyhello
gcc main.c libmyhello.a -o hello
gcc -o main.c # 先生成 main.o
gcc -o hello main.o libmyhello.a
./hello,执行程序:
删除 libmyhello静态库,再次执行 hello 程序
rm libmyhello.a # 删除libmyhello.a
./hello # 运行hello程序
4 .o文件创建动态库,并在程序中使用
a. .o文件创建动态库
动态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为 .so。如:libmyhello.so
gcc -shared -fPIC -o libmyhello.so hello.o # 生成动态库
ls # 查看
b. 在程序中使用动态库
1gcc -o hello main.c -L. -lmyhello
2gcc main.c libmyhello.so -o hello
但运行 hello程序时将会报错(在/usr/lib 中找不到该库文件)
再次生成以下 libmyhello.a 静态库
仍然报错
将文件 libmyhello.so 移动到目录/usr/lib 中可以解决这问题
二、动态库和静态库生成可执行文件大小的对比
创建一个 test2文件夹,并在该文件夹中分别创建子程序 sub1.h、sub1.c、sub2.h、sub2.c、main.c
mkdir test2
cd test2
vim sub1.h
vim sub1.c
vim sub2.h
vim sub2.c
vim main.c
分别建立成:
1,
#ifndef SUB1_H
#define SUB1_H
float x2x(int a, int b);
#endif //SUB1_H
2,
#include"sub1.h"
float x2x(int a, int b){
return a + b; //相加
}
3,
#ifndef SUB2_H
#define SUB2_H
float x2y(int a, int b);
#endif //SUB2_H
4,
#include"sub2.h"
float x2y(int a, int b){
return a * b; //相乘
}
5,
#include<stdio.h>
#include"sub1.h"
#include"sub2.h"
int main(){
int a = 2, b = 3;
printf("%d + %d = %f\n", a, b, x2x(a, b));
printf("%d × %d = %f\n", a, b, x2y(a, b));
return 0;
}
2
用静态库文件进行链接,生成可执行文件
a. 将 sub1.c、sub2.c 编译成 .o文件
b. .o文件创建静态库
c. 在程序中使用静态库
``gcc main.c libsub1.a libsub2.a -o main1
./main1
3,
用动态库文件进行链接,生成可执行文件
a. .o文件创建动态库
gcc -shared -fPIC -o libsub1.so sub1.o
gcc -shared -fPIC -o libsub2.so sub2.o
ls
b. 在程序中使用动态库
gcc main.c libsub1.so libsub2.so -o main2
#将文件 libsub1.so、libsub2.so 移动到目录/usr/lib 中
sudo mv libsub1.so /usr/lib
sudo mv libsub2.so /usr/lib
./main2
d. 两个可执行文件大小的比较
gcc -static main.c libsub1.a libsub2.a -o main1 # 重新由静态库生成
size main1
ldd main1
size main2
ldd main2
三、gcc编译器是怎么编译的
a. 预编译(将源文件 hello.c 文件预处理生成 hello.i)
gcc -E hello.c -o hello.i
1
b. 编译(将预处理生成的 hello.i 文件编译生成汇编程序 hello.s)
gcc -S hello.i -o hello.s
1
c. 汇编(将编译生成的 hello.s 文件汇编生成目标文件 hello.o)
gcc -c hello.s -o hello.o
as -c hello.s -o hello.o
d. 链接(分为静态链接和动态链接,生成可执行文件)
gcc hello.c -o hello
gcc -static hello.c -o hello
e. 用 size 查看文件大小,ldd链接了那些动态库
3,ELF 文件的分析
a. 一个典型的 ELF 文件包含下面几个段
(1) .text:已编译程序的指令代码段
(2) .rodata:ro 代表 read only,即只读数据(譬如常数 const)
(3) .data:已初始化的 C 程序全局变量和静态局部变量
(4) .bss:未初始化的 C 程序全局变量和静态局部变量
(5) .debug:调试符号表,调试器用此段的信息帮助调试
readelf -S hello
b. 反汇编 ELF
objdump -S 将其反汇编并且将其 C 语言源代码混合显示出来:
在ubuntu中下载安装nasm,对示例汇编代码“hello.asm”编译生成可执行程序,并与上述用C代码的编译生成的可执行程序大小进行对比
命令直接下载NASMsudo apt-get install nasm
查看是否安装成功:
编译汇编 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 ; 调用内核功能
c. 汇编与C代码的编译生成的可执行程序大小对比
四、Linux 环境下C语言编译实现贪吃蛇游戏
a. 了解Linux 系统中终端程序最常用的光标库(curses)
1,initscr(): initscr() 是一般 curses 程式必须先呼叫的函数, 一但这个函数被呼叫之后, 系统将根据终端机的形态并启动 curses 模式
2,endwin(): curses 通常以呼叫 endwin() 来结束程式. endwin() 可用来关闭curses 模式, 或是暂时的跳离 curses 模式
3,refresh(): refresh() 为 curses 最常呼叫的一个函式
4,move(y,x): 将游标移动至 x,y 的位置
5,echochar(ch)/addch(ch): 显示某个字元
Ubuntu18.04 安装curses库
sudo apt-get install libncurses5-dev
查看 curses库安装位置
贪吃蛇代码链接进入www.linuxidc.com/Linux/2011-08/41375.htm
./mysnake运行。
总结:让我这个纯小白,更加了解了,Ubuntu。同时了解了如何用 gcc 生成静态库和动态库。
参考博主:在下摸鱼怪。