Ubuntu中gcc的一些使用

本文详细介绍了如何使用GCC在Linux下生成和使用静态库(.a)与动态库(.so),包括创建、链接、及常见问题的解决。同时涵盖了GCC编译工具集的各个组件及其在C/C++程序开发中的应用,如预处理、汇编和链接,以及nasm汇编编译。此外,还涉及第三方库如curses的使用和一个终端游戏的开发实例。
摘要由CSDN通过智能技术生成

一.gcc:.a静态库和.so动态库的生成与使用

1.1用gcc生成静态库和动态库

在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o文件。
(1)第1步:编辑生成例子程序hello.c,hello.h,main.c
先用mkdir命令创建一个目录

#mkdir test1
#cd test1

在这里插入图片描述
用vim编辑器生成三个文件
在这里插入图片描述
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;
}

(2)第 2步,将hello.c编译成.o文件

gcc -c heloo.c

用ls命令查看是否生成hello.o文件
在这里插入图片描述
在命令中看到hello.o文件,本步骤完成
下面先来看看如何创建静态库
(3)第3步 由.o文件创建静态库
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将 创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a。在创建和使用静态库时, 需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文件 libmyhello.a。

ar -crv libmyhello.a hello.o

用ls命令查看是否生成
在这里插入图片描述
ls命令结果中有libmyhello.a
(4)第4步:在程序中使用静态库
静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包 含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从 静态库中将公用函数连接到目标文件中。注意,gcc 会在静态库名前加上前缀 lib,然后追 加扩展名.a 得到的静态库文件名来查找静态库文件。 在程序 main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用 公用函数 hello。下面先生成目标程序 hello,然后运行 hello 程序看看结果如何。
有如下三个方法
方法一:

 gcc -o hello main.c -L. –lmyhello 

自定义的库时,main.c 还可放在-L.和 –lmyhello 之间,但是不能放在它俩之后,否则会提 示 myhello 没定义,但是是系统的库时,如 g++ -o main(-L/usr/lib) -lpthread main.cpp 就不出错。

方法二:

gcc main.c libmyhello.a -o hello

方法三: 先生成 main.o:

gcc -c main.c 

再生成可执行文件:

gcc -o hello main.o libmyhello.a 

动态库连接时也可以这样做。
我们删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中了
在这里插入图片描述
程序照常运行,静态库中的公用函数已经连接到目标文件中了。
我们继续看看如何在 Linux 中创建动态库。我们还是从.o 文件开始
(5) 第 5 步:由.o 文件创建动态库文件。 动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其 文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyh ello.so。用 gcc 来创建动态库。 在系统提示符下键入以下命令得到动态库文件 libmyhello.so。

gcc -shared -fPIC -o libmyhello.so hello.o(-o 不可少)

用ls命令查看
在这里插入图片描述
(6) 第 6 步:在程序中使用动态库; 在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含 这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我 们先运行 gcc 命令生成目标文件,再运行它看看结果。

gcc -o hello main.c -L. -lmyhello

这里报错
在这里插入图片描述
(或 #gcc main.c libmyhello.so -o hello 不会出错(没有 libmyhello.so 的话,会出错),但是 接下来./hello 会提示出错,因为虽然连接时用的是当前目录的动态库,但是运行时,是到 /usr/lib 中找库文件的,将文件 libmyhello.so 复制到目录/usr/lib 中就 OK 了)

./hello: error while loading shared libraries: libmyhello.so: cannot open shar ed object file: No such file or directory 

错误提示,找不到动态库文件 libmyhello.so。程序在运行时, 会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提 示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录/usr/lib 中。

mv libmyhello.so /usr/lib

再输入./hello命令
在这里插入图片描述
成功了。这也进一步说明了动态库在程序运行时是需要的。 我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的 gcc 命令完全一样, 那当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?抱着对问题必究到底的心情, 来试试看。 先删除除.c 和.h 外的所有文件,恢复成我们刚刚编辑完举例程序状态。
使用命令

rm -f hello hello.o /usr/lib/libmyhello.so

用ls命令查看
在这里插入图片描述
再来创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so。

gcc -c hello.c 
ar -cr libmyhello.a hello.o (或-cvr ) 
gcc -shared -fPIC -o libmyhello.so hello.o 

用ls命令查看在这里插入图片描述
通过上述最后一条 ls 命令,可以发现静态库文件 libmyhello.a 和动态库文件 libmyhello.s o 都已经生成,并都在当前目录中。然后,我们运行 gcc 命令来使用函数库 myhello 生成目 标文件 hello,并运行程序 hello。 # gcc -o hello main.c -L. –lmyhello (动态库和静态库同时存在时,优先使用动态库,当然,如果直接 #gcc main.c libmyhello.a -o hello 的话,就是指定为静态库了)

./hello
 ./hello: error while loading shared libraries: libmyhello.so: cannot open shar ed object file: No such file or directory

从程序 hello 运行的结果中很容易知道,当静态库和动态库同名时,gcc 命令将优先使用动 态库,默认去连/usr/lib 和/lib 等目录中的动态库,将文件 libmyhello.so 复制到目录/usr/lib 中即可。
补充 1:编译参数解析 最主要的是 GCC 命令行的一个选项: -shared 该选项指定生成动态连接库(让连接器生成 T 类型的导出符号表,有时候也生成 弱连接 W 类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。
-fPIC 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载 入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L. 表示要连接的库在当前目录中;(多个库:在编译命令行中,将使用的静态库文件放在 源文件后面就可以了。比如:

gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop 

其中-L/usr/lib 指定库文件的查找路径。编译器默认在当前目录下先查找指定的库文件,如 前面的“法二

gcc main.c libmyhello.a -o hello -lmyhello 

编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上 lib,后面 加上.so 或.a 来确定库的名称 libmyhello.so 或 libmyhello.a。 LD_LIBRARY_PATH 这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有 root 权限的话,可以修改/etc/ld.so.conf 文件,然后调用 /sbin/ldconfig 来达到 同样的目的,不过如果没有 root 权限,那么只能采用输出 LD_LIBRARY_PATH 的方法了。 调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include 进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过 ldd 命令察看 时,就是死活找不到你指定链接的 so 文件,这时你要作的就是通过修改 LD_LIBRARY_PATH 或者/etc/ld.so.conf 文件来指定动态库的目录。通常这样做就可以解决 库无法链接的问题了。
补充 2: 从上述可知,如何找到生成的动态库有 3 种方式:
(1)把库拷贝到/usr/lib 和/lib 目录下。
(2)在 LD_LIBRARY_PATH 环境变量中加上库所在路径。 例如动态库 libhello.so 在/home/example/lib 目录下: e x p o r t L D L I B R A R Y P A T H = exportLD_LIBRARY_PATH= exportLDLIBRARYPATH=LD_LIBRARY_PATH:/home/example/lib
(3) 修改/etc/ld.so.conf 文件,把库所在的路径加到文件末尾,并执行 ldconfig 刷新。这样, 加入的目录下的所有库文件都可见。 附:像下面这样指定路径去连接系统的静态库,会报错说要连接的库找不到: g++ -o main main.cpp -L/usr/lib libpthread.a
必须这样 g++ -o main main.cpp -L/usr/lib -lpthread 才正确 。 自定义的库考到/usr/lib 下时, g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a 会出错,但是这样 g++ -o main main.cpp -L/usr/lib -lp hread -lthread -lclass 就正确了。
1.2 Linux 下静态库.a 与.so 库文件的生成与使用
先创建一个作业目录,保存本次练习的文件。

mkdir test2 
cd test2

在这里插入图片描述

然后用 vim编辑器编辑生成所需要的四个文件 A1.c 、 A2.c、 A.h、 test.c 。
在这里插入图片描述

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

test.c:

#include <stdlib.h> 
#include "A.h" 
int main()
{ print1(1); print2("test"); 
exit(0); }

1.2.1、静态库.a 文件的生成与使用。
生成目标文件.o

gcc -c A1.c A2.c

在这里插入图片描述
生成静态库.a 文件

ar crv libafile.a A1.o A2.o

在这里插入图片描述
使用.a 库文件,创建可执行程序

gcc -o test test.c libafile.a
./test

在这里插入图片描述
共享库.so 文件的生成与使
生成目标文件 (此处生成.o 文件必须添加"-fpic"(小模式,代码少),否则在生成.so 文件时会出错)

gcc -c -fpic A1.c A2.c

生成共享库.so 文件

gcc -shared *.o -o libsofile.so

使用.so 库文件,创建可执行程序

gcc -o test test.c libsofile.so 
./test

在这里插入图片描述

发现出现错误:

./test: error while loading shared libraries: libsofile.so: cannot open shared object file: No such file or directory

运行 ldd test,查看链接情况 ldd test linux-vdso.so.1 => (0x00007fff0fd95000) libsofile.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f937b5de000) /lib64/ld-linux-x86-64.so.2 (0x0000563f7028c000) 发现确实是找不到对应的.so 文件。 这是由于 linux 自身系统设定的相应的设置的原因,即其只在/lib and /usr/lib 下搜索对应 的.so 文件,故需将对应 so 文件拷贝到对应路径。

sudo cp libsofile.so /usr/lib 

再次执行./test,即可成功运行。
在这里插入图片描述

2.编写x2x函数,x2y函数

2.1.先创建一个作业目录,保存本次练习的文件。

mkdir work
cd work

在这里插入图片描述
2.2.然后用 vim文本编辑器编辑生成所需要的四个文件main.c 、x2x.c、main.h、x2y.c 。
在这里插入图片描述
2.3 生成目标文件
gcc -c x2x.c x2y.c
在这里插入图片描述
2.4 静态库.a文件生成
gcc ar crv libwork.a x2x.o x2y.o
在这里插入图片描述
2.5使用.a 库文件,创建可执行程序
gcc -o main main.c libwork.a
./main
在这里插入图片描述
2.6动态库.so文件生成
gcc -c -fpic x2x.c x2y.c
2.7 生成共享.so文件
2.8使用.so 库文件,创建可执行程序
错误解决方法同上
在这里插入图片描述
文件大小
使用命令 ls -l
静态库:在这里插入图片描述
动态库:
在这里插入图片描述
比较发现静态库比动态库小,生成的可执行文件也比较小。

二.gcc编译工具集中各软件的用途

1.Linux GCC 常用命令

1.1 简介
GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C 语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL 语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语 言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另 一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。
1.2 简单编译
示例程序如下:

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

这个程序,一步到位的编译指令是:

gcc test.c -o test

在这里插入图片描述

实质上,上述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译 (Compilation)、汇编 (Assembly)和连接(Linking)。
1.2.1 预处理

gcc -E test.c -o test.i
or gcc -E test.c

可以输出 test.i 文件中存放着 test.c 经预处理之后的代码。
在本例中,预处理结果就是将 stdio.h 文件中的内容插入到 test.c 中了。
1.2.2 编译为汇编代码(Compilation)
预处理之后,可直接对生成的 test.i 文件编译,生成汇编代码:

gcc -S test.i -o test.s gcc

的-S 选项,表示在程序编译期间,在生成汇编代码后,停止,-o 输出汇编代码文件。 2.3 汇编(Assembly)
对于上一小节中生成的汇编代码文件 test.s,gas 汇编器负责将其编译为目标文件,如下:

gcc -c test.s -o test.o 

2.4 连接(Linking)
gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。
3 多个程序文件的编译
通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用 GCC 能够很好地管理 这些编译单元。假设有一个由 test1.c 和 test2.c 两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序 test,可以使用下面这条命令:

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 

2.GCC 编译器背后的故事

2.1本文所要介绍的将 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:主要的作用是反汇编。
(8) readelf:显示有关 ELF 文件的信息。
(9) size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小 等
2.2
(一)准备工作 由于 GCC 工具链主要是在 Linux 环境中进行使用,因此本文也将以 Linux 系统作 为工作环 境。为了能够 演示编译的整个 过程,先创建一 个工作目录 test1,然后 用文本编辑器生成一个 C 语言编写的简单 Hello.c 程序为示例,其源代码如下所 示:

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

在这里插入图片描述

(二) 编译过程
1.预处理 预处理的过程主要包括以下过程:
(1) 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编 译指令,比如#if #ifdef #elif #else #endif 等。
(2) 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
(3) 删除所有注释“//”和“/* */”。
(4) 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
(5) 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。 使用 gcc 进行预处理的命令如下: $ gcc -E hello.c -o hello.i
3.汇编 汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o 的目标文件中。
4.链接 链接也分为静态链接和动态链接,其要点如下:
(1) 静态链接是指在编译阶段直接把静态库加入到可执行文件中去。为创建可执行文件,链接器必须要完成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和 重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
(2) 动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统 中把相应动态库加载到内存中去。

2.nasm汇编编译器编译

2.1安装nasm
输入命令

sudo apt install nasm

v
2.2
输入命令

vim 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         ; 调用内核功能

输入命令行

nasm -f elf64 heello.asm
ld -s -o hello hello.o

2.3输入./hello编译
v
2.4 比较大小
hello.asm”编译生成可执行程序
在这里插入图片描述
“hello world”C代码的编译生成的程序
在这里插入图片描述

三.第三方库函数

3.1 curses函数

3.1.1 主要函数功能
curses是一个在Linux/Unix下广泛应用的图形函数库,作用是可以在终端内绘制简单的图形用户界面。
curses使用两个数据结构映射终端屏幕,stdscr和curscr。stdscr是“标准屏幕”(逻辑屏幕),在curses函数库产生输出时就刷新,是默认输出窗口(用户不会看到该内容)。curscr是“当前屏幕”(物理屏幕),在调用refresh函数是,函数库会将curscr刷新为stdscr的样子。

3.1.2 函数基本分为如下几类:
屏幕
  输出到屏幕:
  从屏幕读取字符;
  清除屏幕;
键盘
  键盘模式:
  键盘输入:
窗口
 curses函数库支持在一个物理屏幕上显示多个窗口。

3.2体验一下即将绝迹的远古时代的 BBS

“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统”,然后打开cmd,输入telnet bbs.newsmth.net。
在这里插入图片描述

3.3安装curses

输入命令行

sudo apt-get install libncurses5-dev

在这里插入图片描述

3.4用gcc编译生成一个终端游戏

安装 pacman4console,在终端输入:sudo apt-get install pacman4console。
启动游戏前,建议你先把终端窗口放大,然后输入:pacman4console。运行时用方向键来控制。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值