编译器背后的故事

本文详细介绍了Linux环境下,如何使用gcc生成静态库和动态库,并通过实例演示了静态库和动态库的创建与使用。此外,文章还探讨了gcc编译过程中的常用命令,以及其背后的编译工具如Binutils,特别是addr2line、as、ld和objdump等工具的使用。最后,通过光标库curses和BBS的示例,展示了如何借助第三方库函数完成代码设计,以及如何在Linux下编译实现贪吃蛇游戏。
摘要由CSDN通过智能技术生成

一、用gcc生成静态库和动态库

1、什么是静态库和动态库呢

静态库和动态库是两种共享程序代码的方式。
区别是:
静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;
动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
使用动态库的优点是系统只需载入一次动态库,不同的程序可以得到内存中相同的动态库的副本,因此节省了很多内存。

2、hello实例

1、创建一个目录
2、hello代码
hello.c

#include<stdio.h>
void hello(const char *name)
{
	printf("Hello %s\n",name);
}

hello.h

#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif//HELLO_H

main.c

#include"hello.h"
int main()
{
	hello("everyone");
	return 0;
}

3、gcc编译
在这里插入图片描述

3、静态库使用

创建静态库

创建静态库工具:ar
注意静态库文件名规范
以lib作为前缀,.a文件

程序中使用静态库

1
gcc -o hello main.c -L. -lmyhello
2
gcc main.c libmyhello.a -o hello
3
生成main.o gcc -c main.c
生成可执行文件 gcc -o hello main.c libmyhello.a

验证静态库的特点

在删掉静态库的情况下,运行可执行文件,发现程序仍旧正常运行,表明静态库跟程序执行没有联系。同时,也表明静态库是在程序编译的时候被连接到代码中的。
总体运行
在这里插入图片描述

4、动态库的使用

创建动态库

创建动态库工具:gcc
注意动态库文件名规范
以lib为前缀,.so文件

在程序中执行动态库

gcc -o hello main.c -L. -lmyhello或gcc main.c libmyhello.so -o hello
运行可执行文件hello,出现错误
在这里插入图片描述
将libmyhello.so复制到目录/usr/lib中。在这里插入图片描述
输入密码,成功!
在这里插入图片描述

5、练习

实例一

代码
A1.c

#include<stdio.h>
void print1(int arg)
{
	printf("A1 print arg:%d\n",arg);
}

A2.c

#include<stdio.h>
void print1(int arg)
{
	printf("A1 print arg:%d\n",arg);
}

A.h

#include<stdio.h>
void print1(int arg)
{
	printf("A1 print arg:%d\n",arg);
}

test.c

#include<stdio.h>
void print1(int arg)
{
	printf("A1 print arg:%d\n",arg);
}

结果
在这里插入图片描述
程序中使用静态库
在这里插入图片描述
要将test.c中的exit(0)改为return 0,否则会报错
程序中使用动态库
在这里插入图片描述

实例二

代码
sub1.c

float x2x(int a,int b)
{
	float c=0;
	c=a+b;
	return c;
}

sub2.c

float x2y(int a,int b)
{
	float c=0;
	c=a/b;
	return c;
}

sub.h

#ifndef SUB_H
#define SUB_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif

main.c

#include<stdio.h>
#include"sub.h"
void main()
{
	int a,b;
	printf("Please input the value of a:");
	scanf("%d",&a);
	printf("Please input the value of b:");
	scanf("%d",&b);
	printf("a+b=%.2f\n",x2x(a,b));
	printf("a/b=%.2f\n",x2y(a,b));
}

静态库
在这里插入图片描述
动态库
在这里插入图片描述

二、gcc不是一个人在战斗

1、gcc常用命令

实例一

test.c

#include<stdio.h>
int main(void)
{
	printf("HELLO WORLD \n");
	return 0;
}

1、简单编译
在这里插入图片描述
2、实际编译
2.1预编译
test.i的内容
在这里插入图片描述
2.2编译为汇编代码t
test.s的内容
在这里插入图片描述
2.3汇编
gcc -c test.s -o test.o
1.4连接
gcc test.o -o test
在这里插入图片描述
3、多个程序文件的编译
gcc 源文件1 源文件2 -o 生成的可执行文件
在这里插入图片描述
gcc -c 源文件1 -o 生成.o文件1
gcc -c 源文件1 -o 生成.o文件2
gcc 生成.o文件1 生成.o文件2 -o 生成的可执行文件
在这里插入图片描述

2、gcc的伙伴

GCC( GNU C Compiler)是编译工具。本文所要介绍的将 C/C++语言编写的程序转换成为处理器能够执行的二进制代码的过程即由编译器完成。 **Binutils:**一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as 、ld、 ldd 、 readelf 、 size 等。这一组工具是开发和调试不可缺少的工具,分别简介如下:

(1)addr2line :用来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
(2)as:主要用于汇编,有关汇编的详细介绍请参见后文。
(3)ld:主要用于链接,有关链接的详细介绍请参见后文。
(4)ar:主要用于创建静态库。
(5)ldd:可以用于查看一个可执行程序依赖的共享库。
(6)objcopy:将一种对象文件翻译成另一种格式,譬如将 .bin 转换成 .elf、或者将 .elf 转换成 .bin 等。
(7)objdump:主要的作用是反汇编。有关反汇编的详细介绍,请参见后文。
objdump -D test
在这里插入图片描述

(8)readelf:显示有关 ELF 文件的信息,请参见后文了解更多信息。
在这里插入图片描述

(9)size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等,请参见后文了解使用 size 的具体使用实例。
在这里插入图片描述

分析 ELF 文件

1.ELF 文件的段
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
There are 31 section headers, starting at offset 0x19d8:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
……
[11] .init PROGBITS 00000000004003c8 000003c8
000000000000001a 0000000000000000 AX 0 0 4
……
[14] .text PROGBITS 0000000000400430 00000430
0000000000000182 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000004005b4 000005b4
……
2.反汇编 ELF
由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包含的指令和数据,需要使用反汇编的方法。

使用 objdump -D 对其进行反汇编如下:

$ objdump -D hello
……
0000000000400526 : // main 标签的 PC 地址
//PC 地址:指令编码 指令的汇编格式
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
40052a: bf c4 05 40 00 mov $0x4005c4,%edi
40052f: e8 cc fe ff ff callq 400400 puts@plt
400534: b8 00 00 00 00 mov $0x0,%eax
400539: 5d pop %rbp
40053a: c3 retq
40053b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
……
使用 objdump -S 将其反汇编并且将其 C 语言源代码混合显示出来:
$ gcc -o hello -g hello.c //要加上-g 选项
$ objdump -S hello
……
0000000000400526 :

#include <stdio.h>
int main(void)
{
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
printf(“Hello World!” “\n”);
40052a: bf c4 05 40 00 mov $0x4005c4,%edi
40052f: e8 cc fe ff ff callq 400400 puts@plt
return 0;
400534: b8 00 00 00 00 mov $0x0,%eax
}
400539: 5d pop %rbp
40053a: c3 retq
40053b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
……

nasm编译

1、在Ubuntu中安装nasm
在这里插入图片描述
2、编译
使用nasm -f elf hello.asm,会生成一个hello.o文件
在这里插入图片描述
在这里插入图片描述
3、查看生成文件大小
在这里插入图片描述

三、实际程序是如何借助第三方库函数完成代码设计

1、光标库(curses)

1、initscr()
打开curses模式 进入你的终端。
2、endwin()
关闭窗口stdscr
3、noecho()
关闭回显 让键盘输入字符 不显示在终端上。
4、echo()
显示字符。
5、cbreak()
接受单个字符处理 禁止行缓冲。
6、keypad(WINDOW *, bool)
指定窗口 激活功能键 上下左右 F1 F2等。
6、start_color()
打开color模式 。
7、scroll()
指定窗口 接受字符 超过一行自动写入下一行
除了这些函数以外,更多可参考以下资料:
1、
https://www.mkssoftware.com/docs/cur_index.asp
2、https://wenku.baidu.com/view/cfe045282af90242a895e5f1.html#

2、BBS

使用前的准备工作

控制面板—>程序—>启动或关闭Windows功能
在这里插入图片描述
启动telnet client和适用于Linux的Windows子系统
在这里插入图片描述

打开BBS

打开cmd,输入telnet bbs.newsmth.net,以游客方式登录,可以浏览一些消息,进行操作。
在这里插入图片描述
在这里插入图片描述

3、 Linux 环境下C语言编译实现贪吃蛇游戏

准备工作

安装curses库
在这里插入图片描述

编写程序

程序参考:
http://www.linuxidc.com/Linux/2011-08/41375.htm

编译程序

gcc mysnake.c -lcurses -o snakegame

效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值