【Linux】编译器gcc和g++与调试器gdb的使用

2023-11-01

一、Linux编译器-gcc/g++

1.程序运行的四个阶段

我们知道,一个程序被运行起来之后需要经历四个阶段然后才变成一个可执行的程序,他们分别为:预处理,编译,汇编和链接。下面我们将介绍这四个阶段的操作,测试代码如下:

#include <stdio.h>
// 宏定义
#define N 10

int main()
{
	// 测试注释
	// printf("hello 1\n");
	// printf("hello 2\n");
	// printf("hello 3\n");
	// printf("hello 4\n");
	printf("hello 5\n");
	printf("hello 6\n");
	printf("hello 7\n");
	printf("hello 8\n");
	printf("hello 9\n");
	printf("hello 10\n");

	// 测试宏
	printf("%d\n", N);

	// 测试条件编译
#ifdef SHOW
	printf("hello SHOW\n");
#else
	printf("hello DEFAULT\n");
#endif

	return 0;
}

1.1 预处理

预处理也叫预编译,程序在预处理阶段会完成如下操作:

展开头文件,将头文件中的代码拷贝到当前代码中

删除注释

执行条件编译

完成#define定义的符号,宏的替换以及删除

在Linux下我们可以通过如下指令就可以得到经过预处理之后的代码

gcc -E test.c -o test.i

gcc : 表示使用gcc编译器来编译该代码

-E :表示让代码完成预处理之后就停下来

test.c : 我们要编译的代码

test.i:预处理产生的文件一般以.i为后缀

-o test.i :用于指明临时文件的名称(test.i),经过预处理之后它会形成一个名称为test.i的临时文件,而不是输出到终端上

在这里插入图片描述

在这里插入图片描述

我们可以看到,经过预处理之后,头文件stdio.h中的内容全部会被拷贝到test.i中,所以test.i一个有800多行,我们注释的内容也全部被删除,宏定义被替换,条件编译被执行

1.2 编译

程序在编译阶段会完成如下操作:

语法分析

词法分析

语义分析

符号汇总

我们通过如下指令来获取程序编译之后的代码:

gcc -S test.i -o test.s

-s:表示让代码在完成编译之后停下来,不再继续之后后面的过程

编译产生的文件一般以.s为后缀

在这里插入图片描述

在这里插入图片描述

我们可以看到,编译阶段会将高级语言转换成汇编语言

1.3 汇编

汇编阶段会将编译阶段生成的汇编代码转换成计算机可以识别的二进制目标代码,其中生成的.o文件被称为可重定向的二进制目标文件

我们通过如下指令来获取程序汇编之后的代码:

gcc -c test.o -o test.s

-c:表示让代码在完成汇编之后停下来,不再继续往后执行

汇编产生的文件一般以.s为后缀

在这里插入图片描述

在这里插入图片描述

这样我们就得到了二进制目标文件,但我们使用一般的文本编辑器打开发现是一堆我们看不见的符号,我们可以通过如下指令以指定的格式打开它(默认是八进制):

od test.o

在这里插入图片描述

1.4 链接

程序在链接的过程中会完成如下操作:

1.合并段表,编译器会把汇编阶段生成的多个文件中相同格式的数据合并到一起,最终形成一个.exe文件

2.符号表的合并和重定位,符号表的合并是编译器会把在汇编阶段生成的多个符号表合成一个符号表,重定位是当同一个符号出现在两个符号表中时,编译器会选取其中一个有效的地址相关的一个,舍弃另外一个

在Linux中,链接我们直接使用gcc即可,如果我们没有指定将生成的临时文件的话,默认会将结果存放在a.out中

gcc test.o //默认
gcc test.c -o test.out // 保存到test.out文件中

在这里插入图片描述

在这里插入图片描述

我们最后通过链接得到的文件称为可执行程序,它里面保存的也是计算机能够识别的二进制指令

我们两个文件都可以输出最终的结果:

在这里插入图片描述

虽然我们将gcc编译代码分为预处理,编译,汇编和链接,但是我们在日常生活中运行代码时,直接使用"gcc test.c -o test.out"或者"gcc test.c"即可,这里分为四个阶段是为了让我们更好的理解程序的运行过程

2.链接方式与函数库

2.1 动态链接与静态链接

我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现“printf”函数的呢?

最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用

这是因为我们在编写代码的时候,除了自己实现的函数外,比如printf函数我们会去调用函数库中的代码,但是stdio.h里只有函数的声明,没有函数的实现。此外,程序在预处理,编译和汇编阶段处理的都是我们自己的代码,只有在链接的时候,库函数的实现才会和我们的代码关联起来(符号表的重定位),所以,链接的本质是我们在调用库函数的时候如何与标准库相关联的问题,程序一共有两种链接方式:动态链接和静态链接

动态链接

动态链接是指执行代码的时候,如果遇到库函数调用就跳转到动态库中对应的函数的定义的地方,然后执行该函数,执行完毕后再跳转到原程序处然后继续往后执行,它的优点就是形成的可执行程序小,缺点是受动态库变动(删除,升级等)的影响

静态链接

静态链接是直接将程序需要使用的库函数都从对应的静态库中拷贝一份到原程序中,它的优点是不受静态库(删除,升级等)的影响,缺点是形成的可执行程序大

2.2 动态库与静态库

函数库是一些事先写好的,用于给别人使用的函数的集合,函数库分为静态库和动态库

静态库

静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。在Linux在其后缀名一般为“.a“,在windows在的后缀名为”.lib“

动态库

动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。在Linux下动态库一般后缀名为“.so”,在windows下其后缀名为“.dll”

【注意】

1.动态链接必须使用动态库,静态链接必须使用静态库,即进行动态链接时只能跳转到动态库中对应的函数实现处,进行静态链接时只能拷贝静态库中的函数实现

2.gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,gcc默认生成的二进制程序,是动态链接的.原因如下:

  • 如果使用静态链接,那么程序形成的可执行程序大,此时,不仅仅占用磁盘的空间大,其被运行时加载到内存所占用的空间也会很大,而我们的内容有限,运行过大的程序可能会导致卡死无法运行
  • 虽然动态链接受库函数变动的影响,但是库函数一般是很少变动的,即使变动也会兼容以前的版本,所以对程序的影响不大
  • 在Linux下,我们可以使用"file"指令来辨别文件的类型,使用"ldd"来打印或查看程序运行所 需要的共享库

在这里插入图片描述

Linux一般会自动安装C语言动态库,因为Linux下的大多数指令以及我们默认使用的gcc编译得到的可执行程序都是动态链接的,依赖于C动态库,但是C静态库,C++静态库可能就需要我们自己进行安装。我们可以使用如下命令来安装C和C++的静态库:

sudo yum install -y glibc-static
sudo yum install -y libstdc++-static

我们可以使用"-static"选项来指定程序使用静态方式进行链接:

在这里插入图片描述

我们可以看到,使用静态链接方式形成的可执行程序比动态链接方式形成的可执行程序的要大100倍左右,即一个动态链接只有1M的文件,使用静态链接方式形成的可执行程序就有几百M,两者之间相差非常大,所以Linux默认使用静态链接的方式

3.gcc/g++的使用

在Linux在,我们可以使用如下指令来安装gcc和g++:

sudo yum install -y gcc
sudo yum install -y gcc-g++ libstdc++-devel

gcc与g++的使用如下:

-E 只激活预处理,在完成预处理后停下来,不再往后继续执行,生成以.i为后缀的文件

-S 在完成编译后停下来,不再继续往后执行,生成的文件以.o为后缀 编译到汇编语言不进行汇编和链接

-c 在完成汇编之后停下来,不再继续往后执行,生成以.o为后缀的文件 编译到目标代码

-o 将输出到终端的内容保存到指定的文件中

-static 此选项对生成的文件采用静态链接

-g 以debug的方式发布软件,生成调试信息。GNU 调试器可利用该信息。

-shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.

-O0-O1-O2-O3 编译器的优化选项的4个级别

-O0表示没有优化,这是默认的编译选项

-O1为缺省值,编译器会在不花费太多编译时间的同时试图生成更快更小的代码。这些优化是非常基础的,但一般这些任务肯定能顺利完成

-O2 -O2会比-O1启用多一些标记。设置了-O2后,编译器会试图提高代码性能而不会增大体积和大量占用的编译时间

-O3优化级别最高 在O2的基础上进行更多的优化,用这个选项会延长编译代码的时间,并且在使用gcc4.x的系统里不应全局启用

-w 不生成任何警告信息。

-Wall 生成所有警告信息。

二、Linux调试器–gdb

1.debug与release

在windows中使用VS的时候我们知道,程序的发布方式一共有两种-debug模式和release模式,其中debug模式是给程序员使用的,其中包含调试的各种信息,程序员可以根据这些调试信息对程序进行修改和完善,而release模式则是给用户使用的,它不包含调试信息,因为用户不负责也不关心对程序进行调试。

Linux gcc/g++出来的二进制程序,默认是release模式,如果我们要使用gdb调试,必须在源代码生成二进制程序的时候, 加上 -g 选项,标识以debug方式发布

#include <stdio.h>
#include <time.h>

int AddToVal(int from,int to)
{
    int sum=0;
    int i=0;
    for(i=from;i<=to;++i)                                                                     {
        sum+=i;
    }
    return sum;
}
void Print(int sum)
{
    long long timestamp=time(NULL);
    printf("result=%d,timestamp:%lld\n",sum,timestamp);
}
int main()
{
    int sum=AddToVal(0,100);
    Print(sum);
    return 0;
}

在这里插入图片描述

在这里插入图片描述

我们可以发现,以debug方式发布的和以release方式发布的程序无论是在大小,程序内部的包含的相关的调试信息,还是debug模式下是否具有调试样例都是由明显区别的

2.gdb 的使用

当我们使用-g选项的时候就可以得到以debug模式发布的可执行程序,然后我们就可以使用gdb对其进行调试了:

gdb的安装

在Linux在,我们可以使用如下指令来安装gdb:

sudo yum install -y gdb

gdb调试的常见选项如下:

list/l 行号:显示binFile源代码,接着上次的位置往下列,每次列10行。我们第一次使用l显示源代码后,我们下一次再使用l或者下一次使用enter时,它会接着上一次的位置接着向下显示

list/l 函数名:列出某个函数的源代码。

r或run:运行程序。调试运行,如果程序中有断点,则在断点处停下来,如果没有,则直接 将程序执行完毕,相当于VS中的F5

n 或 next:单条执行。逐过程调试,相当于VS的F10

s或step:进入函数调用 逐语句调试,相当于VS的F11

break(b) 行号:在某一行设置断点,相当于VS的F9

break 函数名:在某个函数开头设置断点

info break :查看断点信息。

finish:执行到当前函数返回,然后停下来等待命令 把当前函数执行完毕

print§:打印表达式的值,通过表达式可以修改变量的值或者调用函数

p 变量:打印变量值。

set var:修改变量的值

continue(或c):从当前位置开始连续而非单步执行程序 运行至下一个断点处停下来,如果断点所在行不是一条语句,比如"{" “}” 或者空行,那么它会继续往后继续执行到有效行处

run(或r):从开始连续而非单步执行程序

delete breakpoints:删除所有断点

delete breakpoints n:删除序号为n的断点 (d 断点编号),每个断点都有自己的编号,我们删除断点时需要指明对应的断点编号

disable breakpoints:禁用断点

enable breakpoints:启用断点

info(或i) breakpoints:参看当前设置了哪些断点

display 变量名:跟踪查看一个变量,每次停下来都显示它的值

undisplay:取消对先前设置的那些变量的跟踪

until X行号:跳至X行

breaktrace(或bt):查看各级函数调用及参数

info(i) locals:查看当前栈帧局部变量的值

quit:退出gdb

我们重点掌握下面的即可:

在这里插入图片描述

指令演示

l行号显示源代码

在这里插入图片描述

l 函数显示该函数的源代码

在这里插入图片描述

r 运行程序

在这里插入图片描述

b 行号打断点,info b查看断点,d 断点编号 删除断点

在这里插入图片描述

r 调试运行

在这里插入图片描述

n 逐过程调试,s逐语句调试

在这里插入图片描述

c 运行至下一个断点处停下

在这里插入图片描述

bt 查看调用堆栈

在这里插入图片描述

p 变量,查看变量值,display 跟踪查看变量 undisplay取消跟踪

在这里插入图片描述

finish 把当前函数运行完,q 退出gdb1

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux】编译器gcc和g++与调试器gdb的使用 的相关文章

  • 为什么我会收到未找到分析器的警告?

    我创建了一个玩具项目来检查最新的 NET 7 预览版 5 和正则表达式代码生成 它效果很好 所以我对现有项目应用了相同的更改 不是为了生产 而是为了个人生产力 由于某种原因 我收到这些警告 CS8032 An instance of ana
  • 使用 Json.NET 序列化子类

    我正在尝试使用 Json NET 序列化子类 生成的 json 包含超类的序列化属性 但是not子类对象的属性 这似乎与我发现的一个问题有关这里就这样 https stackoverflow com q 5863496 498969 但必须
  • 生成多个随机数

    我想生成 25 个唯一的随机数并将它们列在控制台中 数字的长度应至少为 10 个字符 有什么简单的方法可以做到这一点吗 尝试将数字构建为字符串 并使用 HashSet 确保它们是唯一的 Random random new Random Ha
  • ASP.NET Web 应用程序中的身份验证遇到问题

    我正在尝试对从登录页面登录我的 Web 应用程序的用户进行身份验证 我正在使用本教程 http support microsoft com kb 301240作为指南 它几乎准确地解释了我希望做什么 但是当我输入用户名和密码时 验证不起作用
  • 深拷贝和动态转换 unique_ptr

    假设我有一个如下所示的类 class A virtual A class B public A class C public A 我还有一个 unique ptr 向量 它是这样声明的 std vector
  • 局部函数声明有什么用处吗?

    大多数像我这样的 C 程序员都曾犯过以下错误 class C int main C c declares a function c taking no arguments returning a C not as intended by m
  • 嵌入资源文件的路径

    我的资源文件中有一个图标 我想引用它 这是需要图标文件路径的代码 IWshRuntimeLibrary IWshShortcut MyShortcut MyShortcut IWshRuntimeLibrary IWshShortcut W
  • 将 dataGridView 中选定的行作为对象检索

    我有一堂这样的课 public partial class AdressBokPerson public long Session get set public string F rnamn get set public string Ef
  • Visual Studio 中列表框的上移、下移按钮[重复]

    这个问题在这里已经有答案了 我正在尝试制作一个上移按钮和一个下移按钮 以移动 Microsoft Visual Studio 2012 中列表框中的选定项目 我已经在 WDF jquery winforms 和其他一些表单中看到了其他示例
  • 如何将 Q 格式整数转换为浮点数(反之亦然)?

    我四处搜寻 找不到一个很好的问题来回答这个问题 给定一个整数 使用Q Format https en wikipedia org wiki Q number format 如何将该数字转换为普通浮点类型 反之亦然 如何将浮点类型转换为Q F
  • 我可以将 UseCSharpNullComparisonBehavior 用于单个查询吗?

    我有一个查询 该查询曾经是存储过程 现已转换为 EF 查询 现在已经超时了 使用 SQL Profiler 我可以看到生成的 SQL 的唯一区别是 EF 转变的新行为entity Property value into entity Pro
  • 配置:错误:无法运行C编译的程序

    我正在尝试使用 Debian Wheezy 操作系统在我的 Raspberry Pi 上安装不同的软件 当我运行尝试配置软件时 我尝试安装我得到此输出 checking for C compiler default output file
  • 更改 Xamarin.Forms 应用中顶部栏和底部栏(ControlsBar、StatusBar)的颜色

    无论如何 即使后面需要特定于平台的代码 也可以更改顶部栏 蓝色的 和底部栏 黑色的 的颜色吗 我希望添加对浅色和深色模式的支持 因此我希望能够在运行时更改它 有可能的 Android Using Window SetStatusBarCol
  • 在 C# 中使用命名空间别名有什么好处? [复制]

    这个问题在这里已经有答案了 使用命名空间别名有什么好处 仅仅是为了简化编码吗 仅当与类发生冲突时我才使用名称空间别名 对我来说 这根本没有简化 我的意见是 如果没有必要 就不要使用
  • 如何检测应用程序正在运行的 .NET 版本?

    我尝试使用Environment Version ToString 确定目标计算机上正在使用什么 NET 框架 但安装了 4 0 版本时 它说我正在使用 NET 2 0 如何检测目标计算机上正在运行的 NET Framework 版本 En
  • 如何将System.Windows dll添加到Visual Studio 2010 Express?

    我正在开发一个小型应用程序C and VS2010 as IDE with NET框架4 我想用CaptureSource类以便从笔记本电脑的网络摄像头捕获视频 为此我需要添加一个命名空间System Windows DependencyO
  • 当我的进程被终止时到底会发生什么?

    我有一个包含本机代码和托管代码的混合进程 在 Windows Server 2003 上运行 当我从进程资源管理器中终止进程时 它会进入 100 cpu 的状态 并在消失之前保持这种状态一段时间 有时甚至 10 分钟 在此期间我无法 杀死
  • 具有四个 && 的 LINQ Where 子句

    我正在尝试在Where 子句中创建一个带有4 个参数的LINQ 查询 这是一个 Windows 8 应用程序项目 我正在使用 SQLite 数据库 SQLite 实现 https github com praeclarum sqlite n
  • 从对列表创建邻接列表类型结构

    在 C 中 我有 class Pair int val1 int val2 我有一个来自以下来源的配对列表 List
  • 在windows + opengl中选择图形设备

    我知道如何使用 openGL 打开窗口 使用 Win32 或其他工具包 但是当系统有2块显卡时 如何选择要渲染的图形设备 我的编程语言是 C 我专注于 Windows 但任何示例都将受到欢迎 编辑 也许更好地解释我的问题是个好主意 以便添加

随机推荐

  • Java的mkdir()与mkdirs()引发的悲剧---关于java的mkdir()方法无法创建文件目录问题

    昨晚深夜在做项目的文件上传 在上传之前要先判断指定的文件目录是否存在 如果不存在就先创建改目录 因为之前已经做过类似的功能了 所以就把判断文件目录以及创建的代码直接copy过来了 然而很郁闷的是 一模一样的代码 这回却遇到一个特别奇葩的问题
  • 【python】CliffWalking悬崖寻路问题

    强化学习 简介 gym库 CliffWalking SARSA Q learning 示例 SARSA Q learning 简介 机器学习 监督学习 非监督学习 强化学习 模仿人类和动物的试错机制进行学习 智能体与环境交互 根据当前的环境
  • 数据结构面试常见问题总结

    数据结构面试常见问题总结 写在前面 本文记录了一些数据结构面试常见问题 本意用于考研复试 以下面试题为网上整理的问题以及自己加入的一些问题 答案仅供参考 Q 数据结构三要素 A 逻辑结构 物理结构 数据运算 Q 数组与链表有什么区别 A 数
  • innovus中常用命令整理

    restoreDesign load 之前的db list property type 列举出相应的属性 get property 得到相应的object的属性 get pin 获取pin get port 获取port ecoRoute
  • 2023开学礼山东财经大学《乡村振兴战略下传统村落文化旅游设计》许少辉新财经图书馆

    2023开学礼山东财经大学 乡村振兴战略下传统村落文化旅游设计 许少辉新财经图书馆
  • adworld攻防世界 reverse asong

    asong 攻防世界 reverse 进阶区 asong 题目文件 https www jianguoyun com p DQ3g5b4QiNbmBxjX fQC 访问密码 AgV9Sh 主要是集中我们常见的处理方式的整合 注意一个对于ou
  • 进程间通信---管道通信

    进程间通信为什么有那么多不同的方法 资源的不同 所以通信的方式不同 想要获取管道资源 就需要用管道来通信 想要获取消息队列资源 就需要用消息队列来通信 如上所示 一个进程就是一个PCB PCB中的file struct有三个默认文件描述符
  • 【转】 如何提高自己的acm个人能力

    转载自 简单de数字 最终编辑 fading code by zfy0701 本来以为HNU的huicpc035和我一样退役了 后来听说他组成了新的footman队 于是又关注了下他 035体现了两个我觉得非常重要的品质 1 刻苦的训练 2
  • vmtools的安装和使用

    介绍 vmtools工具是在虚拟系统和主机系统进行共享文件夹的工具 1 用root用户登录CentOS后删除桌面的光驱 2 点击菜单栏的虚拟机 gt 安装VMwareTools 3 安装结果如下所示 4 打开VMwareTools 复制VM
  • 【python】Something is wrong with the numpy installation

    2020年2月5日 0次阅读 共448个字 0条评论 0人点赞 QueenDekimZ COCO API windows下安装COCO API时 python setup py build ext install 出现报错 ImportEr
  • Unity中UI框架的使用3-主界面中的弹窗和关闭

    效果图 在主页面点击排位赛按钮 就会弹出图2中的一个弹窗 再点击弹窗右上角的关闭按钮 就会关闭弹窗 回到图3的效果 方法 1 将PopUp这个面板添加到UIPanelType cs文件中 并且将其名称和路径添加到UIPanelType js
  • Python高级函数1:使用 map()、reduce()、filter()、zip() 和 enumerate() 简化代码

    Python高级函数1 使用 map reduce filter zip和 enumerate 简化代码 1 原理 1 1 map 函数 1 2 reduce 函数 1 3 filter 函数 1 4 zip 函数 1 5 enumerat
  • 在分布式环境下标准支付流程的梳理

    支付流程图的梳理 https www processon com diagraming 61a18a895653bb136f893ecc 提交订单 当用户点击立即购买或者提交订单的这个时候数据库就会记录一笔订单 此项业务主要是用到了rabb
  • Android 设置ListView不可滚动 及在ScrollView中不可滚动的设置

    转载请注明出处 http blog csdn net androiddevelop article details 38815493 希望得到的效果是ListView不能滚动 但是最大的问题在与ListView Item还必有点击事件 如果
  • 2023华为OD机试真题【区间交叠/贪心算法】【Python Java C++】

    题目描述 给定坐标轴上的一组线段 线段的起点和终点均为整数并且长度不小于1 请你从中找到最少数量的线段 这些线段可以覆盖住所有线段 输入描述 第一行输入为所有线段的数量 不超过10000 后面每行表示一条线段 格式为 x y x和y 分别表
  • vscode因网络下载失败的问题

    复制出失败的下载链接 https az764295 vo msecnd net stable d045a5eda657f4d7b676dedbfa7aab8207f8a075 VSCodeUserSetup x64 1 72 2 exe 将
  • 多任务视频推荐方案,百度工程师实战经验分享

    推荐系统的应用场景非常广泛 比如非常火爆的短视频推荐 电商平台商品推荐 搜索推荐等 但是你知道吗 短视频APP在向你展示一个你感兴趣的视频之前 通常既要预测你对这个视频是否感兴趣 又要预测你会看多久 点赞还是不点赞 会不会偷偷收藏起来下次接
  • 迁移学习matlab

    迁移学习是一种机器学习技术 它可以利用已有的模型和数据来加速新模型的训练 在Matlab中实现迁移学习 需要先选定一个预训练的模型 然后使用该模型的权重来初始化新模型 最后对新模型进行微调以适应特定的任务 Matlab中有一些已经预先训练好
  • 常用函数式接口

    常用函数式接口 JDK 8 中重要的函数接口 接口 参数 返回 中文 示例 Supplier None T 提供者 工厂方法创建对象 Consumer T void 消费者 输出一个值 Predicate T boolean 谓语 顾名思义
  • 【Linux】编译器gcc和g++与调试器gdb的使用

    文章目录 一 Linux编译器 gcc g 1 程序运行的四个阶段 1 1 预处理 1 2 编译 1 3 汇编 1 4 链接 2 链接方式与函数库 2 1 动态链接与静态链接 2 2 动态库与静态库 3 gcc g 的使用 二 Linux调