007 Linux 开发工具(上)—— vim、解放sudo、gc+

🦄 个人主页 : 小米里的大麦-CSDN博客

🎏 所属专栏 : Linux_小米里的大麦的博客-CSDN博客

🎁 GitHub主页 : 小米里的大麦的 GitHub

⚙️ 操作环境 : Visual Studio 2022

文章目录

    • [Linux 开发工具(上)](#Linux 开发工具(上))
      • [Linux 编辑器 ------ `vim`](#Linux 编辑器 —— vim)
      • [解放 `sudo` 权限](#解放 sudo 权限)
      • [Linux 编译器 ------ `gcc `/ `g++`](#Linux 编译器 —— gcc / g++)
        • [背景知识回顾 ------ C/C++ 程序的编译流程](#背景知识回顾 —— C/C++ 程序的编译流程)
        • [1. 预处理 (Preprocessing)](#1. 预处理 (Preprocessing))
        • [2. 编译 (Compilation)](#2. 编译 (Compilation))
        • [3. 汇编 (Assembly)](#3. 汇编 (Assembly))
    • 传道解惑
      • Q1:什么是可重定位目标文件?
      • Q2:为什么需要可重定位目标文件?
      • Q3:什么时候会被最终链接?
        • [4. 链接 (Linking)](#4. 链接 (Linking))
        • [为什么我们能在 Windows 或 Linux 上进行 C/C++ 开发?](#为什么我们能在 Windows 或 Linux 上进行 C/C++ 开发?)
        • [软件版本管理:社区版 vs 专业版](#软件版本管理:社区版 vs 专业版)
        • 关键命令与文件类型总结
        • 记忆法
      • 函数库的概念与作用
      • [Linux 中的可执行文件链接方式:动态、静态与混合链接](#Linux 中的可执行文件链接方式:动态、静态与混合链接)
      • 小故事理解动静态链接
        • [1. 默认情况下,gcc 采用动态链接](#1. 默认情况下,gcc 采用动态链接)
        • [2. 强制使用静态链接(`-static` 选项)](#2. 强制使用静态链接(-static 选项))
        • [3. `-static` 的本质:改变库的优先级](#3. -static 的本质:改变库的优先级)
        • [4. 混合链接(部分静态,部分动态)](#4. 混合链接(部分静态,部分动态))
        • [5. `-static` 的优缺点](#5. -static 的优缺点)
        • [6. 推荐场景](#6. 推荐场景)
      • [Debug 与 Release 模式 & ELF 可执行文件格式](#Debug 与 Release 模式 & ELF 可执行文件格式)
        • [1. Debug 模式(调试模式)](#1. Debug 模式(调试模式))
        • [2. Release 模式(发布模式)](#2. Release 模式(发布模式))
        • [3. ELF(Executable and Linkable Format,可执行文件格式)](#3. ELF(Executable and Linkable Format,可执行文件格式))
      • 总结
    • 共勉

Linux 开发工具(上)

Linux 编辑器 ------ vim

Vim 是一个强大、多模式的文本编辑器,具有高度的可定制性和丰富的功能。打开文件 :在命令行中输入 vim filename,打开指定文件。Vim 从入门到牛逼 / Vim 从入门到牛逼(备用)

打开当前目录下的文件 code.c,使用命令:vim code.c,同时 支持相对路径和绝对路径 打开:vim home/code.c

Vim 的主要模式:
  • 命令模式(Normal Mode) :命令模式是 Vim 的核心模式,几乎所有操作都从这里开始,启动 Vim 后,默认处于命令模式 。在此模式下,键盘输入被解释为命令而非文本输入 。只能使用方向键、删除、复制、粘贴等操作用来进行文本的导航、选择、删除、替换等。(按 i 进入插入模式,按 : 进入底行模式,按 Esc 从其他模式返回到命令模式)
  • 插入模式(Insert Mode) :按下 i 键进入插入模式。插入模式下可以像普通文本编辑器一样输入文本。按 Esc 键返回命令模式。
  • 底行模式(Ex Mode 或 Bottom Line Mode) :底行模式是 Vim 的强大功能之一,支持复杂的文件操作和配置。按下 shift+ (也就是 :) 键进入底行模式。在底行模式下可以执行文件保存、退出、查找替换等命令。例::wq 保存并退出 Vim。按 Esc 键返回命令模式。(注意: Ctrl+C 也可以退出底行模式并返回命令模式,尽量不要使用,可能会出现未知错误)
Vim 命令模式下的一些常用操作: 史上最全 Vim 快捷键键位图(入门到进阶)

导航

  • gg: 定位光标到最开始行。
  • shift+gG: 定位光标到最结尾行。
  • n+shift+gnG: 定位光标到任意行------跳到指定行号 n
  • shift+$: 定位光标到当前行的结尾。
  • shift+^: 定位光标到当前行的开始。
  • w, b: 光标按照单词进行行内移动,分别向右或向左移动一个单词。
  • h, j, k, l: 分别移动光标左、下、上、右。

文本编辑

  • (n)yy: 复制光标所在行或指定行数的内容。
  • (n)dd: 剪切(删除)当前行或指定行数的内容。
  • (n)p: 粘贴 n (次数)行到光标所在下一行。
  • u: 撤销上一步操作。
  • ctrl+r: 撤销之前的撤销(重做撤销的操作)。

其他操作

  • shift+~: 大小写转换。
  • (n)r: 对光标字符之后的 n 个字符进行批量化替换。
  • shift+R: 进入替换模式,对内容进行整体替换 → 第四种模式。
  • (n)x: 对光标后面的 n 个字符进行删除。
  • :w 保存,:w! 强制保存,:q 退出,:q! 强制退出,:wq! 强制退出并保存

这两个命令 (shift+$shift+^) 被称为"锚点 ",因为它们将光标快速固定到行的特定位置------就像锚点固定物体的位置一样。在文本编辑器中,锚点 通常指的是可以用来快速定位或固定某个位置的标记。

为什么 vim 快捷键不像现在键盘的常规操作:主要是因为早期的键盘并不像现在这样有功能键(F1-F12)、方向键或数字小键盘。Vim 的许多快捷键都是为了适应这些限制而设计的。(可以查找一下那个时代的键盘 🤪)

Vim 多文件编辑与窗口管理

在 Vim 中,你可以同时打开多个文件,并使用 分割窗口 来进行并行编辑。

打开多个文件 ------ 垂直分割窗口 (vs):

  • 作用:在当前窗口 右侧 打开一个新的窗口,并加载指定的文件。
  • 用法::vs file2.c。示例:先打开 file1.c,然后在 Vim 中输入 :vs file2.c,即可在右侧创建一个窗口并打开 file2.c

窗口切换 Ctrl+w w

  • 作用:在多个窗口之间切换光标位置。
  • 操作方式:按 Ctrl+w,再按 w,光标会跳到下一个窗口。如果有多个窗口,重复 Ctrl+w w 可以在所有窗口之间循环切换。

窗口内编辑光标在哪个窗口里面,就对哪一个窗口进行操作 ,你可以在当前窗口进行 插入、删除、复制等编辑操作,而不会影响其他窗口。

  • 水平分割窗口::sp file2.c
  • 调整窗口大小:Ctrl+w +(增加高度)、Ctrl+w -(减少高度)。

简单的 vim 配置(Vim 改装

Vim 的配置文件有两个主要位置,注意:一个用户配置一个 vim 文件,不会互相影响,不建议给 root 做配置,推荐用普通用户!

  1. 全局配置文件/etc/vimrc,对所有用户都生效。
  2. 用户私有配置文件 :每个用户可以在其主目录下创建 .vimrc,仅对该用户生效。例如,/root/.vimrc 适用于 root 用户。

如何修改 .vimrc

  1. 切换到自己的用户目录(确保在自己的 home 目录下):cd ~
  2. 打开 .vimrc 文件(如果不存在,则创建):vim .vimrc

常用 Vim 配置

.vimrc 文件中添加以下配置:

  • 设置语法高亮:syntax on,作用: 开启 Vim 的语法高亮功能,使代码有不同颜色,提高可读性。
  • 显示行号:set nu,作用: 开启行号显示,在左侧显示每行的编号,方便定位代码行。
  • 设置缩进的空格数:set shiftwidth=4,作用: 设定每次缩进的空格数为 4。适用于代码自动缩进,提高代码可读性。

测试配置

修改 .vimrc 文件后,需要重新打开 Vim 或者在 Vim 内输入以下命令让配置生效::source ~/.vimrc 然后可以在 Vim 中测试:

  • 输入 :set nu? 检查行号是否开启。
  • 输入 :set shiftwidth? 检查缩进空格数。

这样就完成了简单的 Vim 配置! 🚀

使用插件(进阶)

Vim 支持插件扩展功能,要配置好看的 vim,原生的配置可能功能不全,可以选择安装插件来完善配置,保证用户是你要配置的用户。Vim 的插件管理工具如 Vim-Plug,一些常用的插件如 NERDTree、coc.nvim 等,以增强 Vim 的功能。

推荐使用 Vim-Plug 作为插件管理器。

  1. 安装 Vim-Plug

    bash 复制代码
    curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
        https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
  2. 配置插件 :在 .vimrc 中添加以下内容:

    bash 复制代码
    call plug#begin('~/.vim/plugged')
    Plug '插件名称'  " 例如:Plug 'preservim/nerdtree'
    call plug#end()
  3. 安装插件

    在 Vim 中运行 :PlugInstall 安装配置的插件。


我的 vim 配置

安装方法:shell 中执行指令(想在哪个用户下让 vim 配置生效,就在哪个用户下执行这个指令。强烈 "不推荐" 直接在 root 下执行),需要按照提示输入 root 密码。您的 root 密码不会被上传,请放心输入。

bash 复制代码
curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh

注意:安装完成后一定要手动执行 source ~/.bashrc 或重启终端,这样配置才能生效! 说明:安装完成后,目录下会有一个名为 install.sh 的配套脚本,处于安全考虑,可以执行 mv install.sh .install.sh 进行隐藏。

默认缩进配置为 2 个字符,将 tab 键换成 4 个字符的方法:到达家目录下执行 vim .vimrc,大概在 50 行进行修改:

bash 复制代码
set tabstop=4       " 每个制表符占用 4 个空格
set softtabstop=4   " 插入制表符时插入 4 个空格
set shiftwidth=4    " 自动缩进和文本块操作时使用 4 个空格

卸载方法: 在安装了 VimForCpp 的用户下执行:

bash 复制代码
bash ~/.VimForCpp/uninstall.sh

解放 sudo 权限

使用 root 账户执行命令:

bash 复制代码
vim /etc/sudoers

按下 i 键进入插入模式,找到大约第 100 行左右的位置(附近会有 root ALL=(ALL) ALL 的字眼),在其下方添加以下内容:

bash 复制代码
username    ALL=(ALL)    ALL
  • username:替换为你要授权的账户名。
  • ALL=(ALL):允许用户以任何用户身份执行命令。
  • ALL:允许执行所有命令。

Esc 键返回到默认模式,然后输入 :wq! 强制保存并退出。

验证 :切换到该用户并测试 sudo 权限:

bash 复制代码
su - username
sudo ls /root

如果 sudo 配置正确,系统会提示输入密码,然后执行命令。


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

背景知识回顾 ------ C/C++ 程序的编译流程

C/C++ 程序从源代码到可执行文件需经历四个主要阶段:预处理 → 编译 → 汇编 → 链接。每个阶段由编译器(如 GCC、Clang 或 MSVC)逐步处理,最终生成可执行文件或库文件。

1. 预处理 (Preprocessing)

预处理是编译的第一步,主要处理源代码中的宏、注释、头文件等。预处理的输出是一个经过宏替换、去注释、头文件展开的中间代码。

主要工作:

  • 去除注释:删除代码中的注释部分。
  • 宏替换 :替换代码中的宏定义(如 #define)。
  • 头文件展开 :将 #include 指令包含的头文件内容插入到当前文件。
  • 条件编译 :根据条件编译指令(如 #ifdef#endif)选择性地包含或排除代码。

输出:

  • 预处理后的文件通常是一个 .i 文件,它是源代码经过宏替换和头文件展开后的中间结果。

命令示例:

bash 复制代码
gcc -E temp.c -o temp.i		# 执行预处理后就停止,`-E` 选项表示只进行预处理,输出 `.i` 文件。

重要说明: 预处理后的文件仍然是 C 语言代码,因此可以说经过预处理后,程序依然是 C 语言程序。

2. 编译 (Compilation)

编译阶段的作用是将预处理后的代码转化为汇编代码。编译器会对代码进行语法分析、语义分析和优化,最终生成与平台架构相关的汇编代码。

主要工作:

  • 语法分析:检查代码是否符合语法规则。
  • 语义分析:检查变量类型、函数调用等语义是否正确。
  • 优化:对代码进行优化,使得生成的汇编代码更高效。

输出:

  • 编译后的文件是一个 .s 文件,包含了平台特定的汇编代码。

命令示例:

bash 复制代码
gcc -S temp.i -o temp.s		# 执行编译后就停止,`-S` 选项表示将预处理后的文件编译成汇编语言。
3. 汇编 (Assembly)

汇编阶段的作用是将汇编代码转换为机器可识别的二进制目标代码。汇编器会将汇编代码翻译为机器指令,生成可重定位目标文件。

主要工作:

  • 汇编:将汇编代码转换为目标代码,生成二进制文件。

输出:

  • 汇编后的文件是一个 .o(Linux)或 .obj(Windows)文件,这些文件是目标文件,包含机器指令,但尚未链接成最终的可执行文件。

命令示例:

bash 复制代码
gcc -c temp.s -o temp.o		# 执行汇编后停止,`-c` 选项表示只进行汇编,不进行链接,输出 `.o` 文件。

重要说明: 可重定位目标二进制文件,简称目标文件,.obj 文件不可以独立执行,虽然已经是二进制了,还需要经过链接才能执行!

传道解惑

在 Linux 中,可重定位目标二进制文件(Relocatable Object File)是一种 中间二进制文件 ,它是由编译器或汇编器生成的,但还 不能直接执行 ,必须经过 链接(Linking) 处理后才能成为最终的可执行文件或库文件。

Q1:什么是可重定位目标文件?

可重定位目标文件通常以 .o 结尾(Windows 上是 .obj),它包含:

  • 机器指令(Machine Instructions):程序的可执行代码,但未指定最终内存地址。
  • 符号表(Symbol Table):记录函数、变量等符号,供链接器解析。
  • 重定位信息(Relocation Information):用于在链接时调整地址。
  • 节(Section)结构 :比如 .text(代码段)、.data(已初始化数据段)、.bss(未初始化数据段)等。

这些文件是 "可重定位" 的,因为它们的地址信息 尚未固定 ,而是由 链接器 在合并多个目标文件时决定最终的内存布局。

Q2:为什么需要可重定位目标文件?

如果编译器直接把 C/C++ 源代码编译成最终可执行程序,那么:

  • 无法将不同的模块(文件)合并,无法进行大规模项目开发。
  • 无法使用动态链接库(Shared Library),会导致程序体积庞大。
  • 无法延迟地址分配,不适用于操作系统的内存管理策略。

所以,编译器通常 生成 可重定位目标文件 ,再由 链接器 进行 地址调整和符号解析,最终得到可执行程序。

示例:生成可重定位目标文件

假设我们有一个简单的 C 代码 temp.c

c 复制代码
#include <stdio.h>

void hello()
{
   printf("Hello, World!\n");
}

第一步:编译但不链接

bash 复制代码
gcc -c temp.c -o temp.o

这样会生成 temp.o,它是一个 可重定位目标文件 ,里面的 hello 还没有绑定到最终的地址。

第二步:使用 readelf 查看目标文件

bash 复制代码
readelf -h temp.o

部分输出:

复制代码
Type:           REL (Relocatable file)
Machine:        x86-64

可以看到 TypeREL,表示它是一个 可重定位文件

Q3:什么时候会被最终链接?

可重定位目标文件 通常不会单独使用,它最终会:

  1. 静态链接(Static Linking) :被 ld(链接器)合并到最终的可执行文件中。
  2. 动态链接(Dynamic Linking) :与共享库(.so 文件)进行链接,在运行时加载。

例如:

bash 复制代码
gcc temp.o -o temp
./temp

这时 temp.o 经过链接后变成可执行文件 temp,可以直接运行。

4. 链接 (Linking)

链接阶段的作用是将多个目标文件(如 .o 文件)和库文件进行链接,生成最终的可执行文件或库文件。链接器会处理符号引用、重定位等工作,确保程序能够正确运行。

主要工作:

  • 符号解析:将不同目标文件中的符号(如函数名、变量名等)进行链接,确保各部分之间的正确引用。
  • 重定位:调整代码中地址的引用,使得目标文件能够在内存中正确加载。

输出:

  • 链接后的输出通常是一个可执行文件(如 a.outprogram.exe)或动态链接库(如 .so.dll 文件)。

命令示例:

bash 复制代码
gcc source.o -o program		# 默认链接 C 标准库,这条命令将目标文件 `temp.o` 链接为可执行文件 `program`。

为什么我们能在 Windows 或 Linux 上进行 C/C++ 开发?

在 Windows 和 Linux 上进行 C/C++ 开发是因为这些操作系统提供了支持编译和执行 C/C++ 代码的工具链。具体而言,需要安装开发环境(如 GCC、Visual Studio 等)以及相关的库文件和头文件。

1. 头文件和库文件

  • 头文件 :定义了语言的核心功能、标准库以及外部库的接口(如 stdio.hstdlib.h)。
  • 库文件 :包含预编译的二进制代码(如 libc.amsvcrt.dll),在链接阶段与用户代码结合。

2. 开发工具

  • 编译器:如 GCC(GNU Compiler Collection)、g++,以及 Visual Studio(VS)等 IDE,提供了代码编辑、编译、调试等功能。
  • 语言支持包安装开发工具时,会自动下载头文件和库。 例如,安装 VS 时选择"C++ 开发",会附带 C++ 标准库(如 STL)和 Windows API 库。

3. 跨平台开发

  • 条件编译 :通过宏(如 #ifdef __linux__)区分不同平台的代码逻辑。
  • 平台专属库 :Linux 依赖 glibc,Windows 依赖 msvcrt.dll。开发者需调用标准接口或使用跨平台库(如 Qt、Boost)。
软件版本管理:社区版 vs 专业版

1. 代码维护策略

  • 单一代码库 :企业无需维护多份代码,而是通过 条件编译 控制功能模块的启用或禁用。例如:

    c 复制代码
    #ifdef PROFESSIONAL_EDITION
      enableAdvancedFeatures();
    #endif
  • 编译参数控制 :构建时通过宏定义(如 -DPROFESSIONAL_EDITION)选择版本。

2. 功能裁剪 ------ 不需要维护两份代码,根据不同的编译条件,会裁剪掉社区版不需要的功能即可

  • 社区版:禁用部分高级功能(如性能分析工具、企业级加密)。
  • 专业版:包含完整功能,通过编译选项开启。

关键命令与文件类型总结
阶段 命令选项 输入文件 输出文件 文件内容
预处理 -E .c .i 展开后的 C 代码
编译 -S .i .s 汇编代码
汇编 -c .s .o(Linux) 目标二进制文件
链接 .o a.out(默认) 可执行文件
记忆法
  • ESC 键记忆法:键盘左上角的 ESC 键可以关联各个阶段的后缀:

    • .i -> 预处理(Preprocessing)
    • .s -> 汇编(Assembly)
    • .o -> 目标文件(Object file)
  • iso → 镜像文件后缀。 最终的可执行文件或库文件就是链接的结果。

    cpp 复制代码
    编译流程:
    .c 源文件 → 预处理 → 编译 → 汇编 → 链接 → 可执行文件
              (-E)     (-S)   (-c)    
            
    多文件编译例如:
    processBar.c → processBar.o ─┐
                                 ├→ processBar
    processBarmain.c → processBarmain.o ─┘

函数库的概念与作用

在 C/C++语言编程中,函数库(Library)允许开发者复用已有的代码,而不必每次都从头开始编写。函数库通常包含一组预先编写好的函数,这些函数可以被多个程序调用。通过使用函数库,开发者可以节省时间,减少重复劳动,并提高代码的可靠性和可维护性。

函数库的分类

函数库主要分为两种类型:静态库动态库

1. 静态库(Static Library)

  • 定义:静态库在编译链接时,会将库文件的代码全部加入到可执行文件中。因此,生成的可执行文件会比较大,但在运行时不再需要库文件。
  • 后缀名 :静态库的后缀名通常为 .a(例如 libmylib.a)。
  • 优点:由于库代码被直接嵌入到可执行文件中,程序运行时不需要依赖外部的库文件,因此具有较好的独立性。
  • 缺点:生成的可执行文件较大,且如果多个程序使用相同的静态库,每个程序都会包含一份库代码的副本,导致内存浪费。

2. 动态库(Dynamic Library)

  • 定义:动态库在编译链接时并不会将库文件的代码加入到可执行文件中,而是在程序运行时由操作系统的运行时链接器动态加载。这样可以节省系统的开销。
  • 后缀名 :动态库的后缀名通常为 .so(例如 libmylib.so)。
  • 优点:生成的可执行文件较小,多个程序可以共享同一个动态库,节省内存和磁盘空间。此外,动态库可以在不重新编译程序的情况下更新。
  • 缺点:程序运行时需要依赖外部的库文件,如果库文件丢失或版本不兼容,程序可能无法运行。
函数库的命名规则

函数库的命名通常遵循一定的规则,以便于识别和使用。常见的命名格式为:

复制代码
libname.so.XXX
  • lib 是前缀,表示这是一个库文件。
  • name 是库的名称,例如 c 表示 C 标准库。
  • .so 表示这是一个动态库(静态库通常使用 .a)。
  • XXX 是版本号,表示库的版本。

例如,libc.so.6 是 C 标准库的动态库,版本号为 6。

函数库的使用

在 C 语言中,常用的函数库如 printf 函数的实现并没有直接包含在源代码中,也没有在头文件 stdio.h 中定义。那么,这些函数是如何被调用的呢?

1. 头文件的作用 :头文件(如 stdio.h)中只包含了函数的声明(即函数原型),告诉编译器这些函数的存在及其参数和返回值的类型。头文件并不包含函数的实现。

2. 库文件的作用 :函数的实现通常位于库文件中。例如,printf 函数的实现位于 C 标准库的动态库 libc.so.6 中。编译器在链接阶段会将这些库文件与代码结合起来,生成最终的可执行文件。

3. 链接过程

  • 静态链接:在编译时,静态库的代码会被直接嵌入到可执行文件中。生成的可执行文件不依赖外部的库文件。
  • 动态链接:在编译时,动态库的代码不会被嵌入到可执行文件中。程序运行时,动态链接器会根据预定义的路径规则去查找这些库文件。
函数库的作用 ------ 不让我们做重复工作,站在巨人的肩膀上享受
  • 代码复用:避免重复造轮子,开发者可以直接使用封装好的函数,而不用自己实现复杂的功能。
  • 隐藏源码:共享库只提供 .so 文件,而不公开 .c 源文件,保护代码的知识产权。
  • 提高程序效率:通过动态链接的方式,多个程序可以 共享同一份库文件,减少内存占用,提高运行效率。

Linux 中的可执行文件链接方式:动态、静态与混合链接

在 Linux 中,gcc 编译形成可执行程序时,默认采用动态链接 ,即程序运行时依赖 .so(共享库),这样可以减少可执行文件的大小,并允许多个程序共享相同的库文件。然而,我们可以通过 静态链接混合链接 的方式来改变默认的链接行为。

小故事理解动静态链接

想象一下,你正在设计一款可以快速适应不同地形的汽车。为了实现这一目标,你决定使用一些通用的动态链接库,比如"越野轮子"、"城市轮胎"和"雪地链"。这些组件可以在不同的车型之间共享,并且可以根据需要在车辆启动时或行驶过程中即时替换。如果一个顾客今天想要一辆适合城市驾驶的汽车,你可以安装"城市轮胎";如果明天他想去越野冒险,只需更换为"越野轮子"。这种灵活性就像动态链接(库),程序运行时按需加载,多个程序共享,节省内存,灵活高效。

另一方面,有些汽车组件一旦安装就不能轻易更改了,例如车身结构或者发动机。这些是根据特定需求量身定做的。当你选择了一种类型的发动机后,它就永久性地成为汽车的一部分,无法在不拆卸整个汽车的情况下进行更换。静态链接库就像发动机,在编译时整合进程序,成为其固定组成部分,提供稳定功能,但程序体积较大,独立运行。

1. 默认情况下,gcc 采用动态链接

当我们编译 C 语言程序:

bash 复制代码
gcc hello.c -o hello

默认情况下,gcc 优先链接动态库.so),并在执行时加载库文件。例如,标准 C 库 libc.so.6 是动态链接的:

bash 复制代码
ldd hello

示例输出:

bash 复制代码
linux-vdso.so.1 =>  (0x00007fffca9fe000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2a4c3c9000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2a4c788000)

这表示 hello 依赖 libc.so.6,而不是 libc.a(静态库)。

2. 强制使用静态链接(-static 选项)

如果想让程序采用 纯静态链接 ,可以使用 -static 选项:

bash 复制代码
gcc hello.c -o hello -static

这时,编译器会 优先查找并链接静态库(.a ,把所有库代码嵌入可执行文件中,使其不再依赖外部 .so 文件。检查:

bash 复制代码
ldd hello

如果是纯静态链接,输出会是:

plaintext 复制代码
not a dynamic executable

这表明 hello 不依赖任何动态库

3. -static 的本质:改变库的优先级

使用 -static 选项时,必须保证所有依赖库的静态版本(.a)都存在 ,否则,编译会失败。如果没有静态库,但强制使用 -static 会报错!如果系统只有静态库,没有动态库 ,即使没有 -staticgcc 仍然会成功编译,并采用静态链接。因为它找不到 .so 文件,只能用 .a

  • 默认情况下gcc 优先选择动态库(.so
  • 加上 -staticgcc 强制使用静态库(.a,如果找不到静态库,则会报错。
  • 并且 -static 是"一次性"的,即:
    • 所有 需要的库都必须静态链接。
    • 不能部分使用动态库,部分使用静态库

如果 -static 选项下某个库没有 .a 文件,编译就会失败。

4. 混合链接(部分静态,部分动态)

虽然 -static 会让所有库都静态链接,但我们可以通过手动指定库的链接方式,实现 混合链接(部分库静态,部分库动态)。

让特定库使用静态链接 :使用 -Wl,-Bstatic 指定部分库静态链接:

bash 复制代码
gcc hello.c -o hello -Wl,-Bstatic -lcustomlib -Wl,-Bdynamic -lc
  • -Wl,-Bstatic-lcustomlib 采用静态库 libcustomlib.a
  • -Wl,-Bdynamic-lc 采用动态库 libc.so(恢复默认动态链接)。
  • 这样就实现了 混合链接(部分库静态,部分库动态)。
5. -static 的优缺点
方式 优点 缺点
动态链接(默认) - 可执行文件小,多个程序共享库,节省磁盘、内存、网络等空间 - 库可以独立更新,不需重新编译程序 - 依赖外部 .so 文件,库缺失时程序无法运行
静态链接(-static) - 程序可以独立运行,无需依赖库文件 - 适用于嵌入式系统或无共享库的环境 - 可执行文件大(是动态的几十上百倍!),占用更多磁盘、内存、网络空间 - 无法通过库更新修复漏洞,需要重新编译
6. 推荐场景
场景 建议链接方式
普通 Linux 应用 默认动态链接(节省空间,便于更新)
需要独立运行的程序 静态链接(-static,避免外部依赖
服务器软件 混合链接,关键库静态,其余动态
嵌入式系统 静态链接,减少依赖

Debug 与 Release 模式 & ELF 可执行文件格式

1. Debug 模式(调试模式)
  • 包含调试信息(如变量名、函数符号表)。
  • 没有优化,代码尽量保持源代码的执行逻辑,方便调试。
  • 可追踪调试 ,可以用 gdb(GNU Debugger)进行断点、变量查看等操作。

编译方式:

bash 复制代码
gcc -g hello.c -o hello_debug
  • -g 选项:生成调试信息,方便 gdb 进行调试。
  • hello_debug:可执行文件,但体积较大,因为包含了额外的调试信息。

调试方式:

bash 复制代码
gdb ./hello_debug		# 可以设置断点、查看变量值等。
2. Release 模式(发布模式)
  • 优化代码,提高执行效率
  • 去除调试信息,减小可执行文件大小
  • 适用于正式发布的程序,但不方便调试。

编译方式:

bash 复制代码
gcc -O2 hello.c -o hello_release
  • -O2(优化等级 2):让编译器优化代码,提高执行速度(-O3 可进一步优化)。
  • 没有 -g,不生成调试信息
3. ELF(Executable and Linkable Format,可执行文件格式)

在 Linux 下,可执行程序形成的时候,不是无顺的二进制构成,可执行程序有自己的二进制格式 ------ ELF 格式。

  • 程序头部(Program Header):描述如何加载程序。
  • 代码段(.text):存放程序的可执行代码。
  • 数据段(.data、.bss):存放全局变量、静态变量等。
  • 符号表(仅 Debug 模式下存在):包含函数、变量等信息,方便调试器使用。

查看 ELF 结构:

bash 复制代码
readelf -h hello_debug   # 查看 ELF 头部信息
objdump -d hello_debug   # 反汇编可执行文件

总结

GCC/G++ 在 Linux 下的常用编译选项

选项 作用 示例
-o <文件名> 指定输出的可执行文件名(gcc -o 输出文件 源文件gcc 源文件 -o 输出文件 均可,更推荐后者!) gcc hello.c -o hello
-c 仅编译,不进行链接,生成 .o 目标文件 gcc -c hello.c -o hello.o
-g 生成调试信息,便于 gdb 调试 gcc -g hello.c -o hello_debug
-O0 不优化,适用于 Debug gcc -O0 hello.c -o hello
-O1 基本优化,适用于一般调试 gcc -O1 hello.c -o hello
-O2 标准优化,提高性能,适用于 Release gcc -O2 hello.c -o hello
-O3 高级优化,可能影响可读性和调试 gcc -O3 hello.c -o hello
-Wall 启用所有常见警告 gcc -Wall hello.c -o hello
-Wextra 启用额外的警告信息 gcc -Wall -Wextra hello.c -o hello
-Werror 把所有警告当作错误 gcc -Werror hello.c -o hello
-static 进行 静态链接 ,不依赖 .so gcc -static hello.c -o hello
-shared 生成 共享库(动态库) .so 文件 gcc -shared -fPIC hello.c -o libhello.so
-fPIC 生成位置无关代码(用于动态库) gcc -fPIC -c hello.c -o hello.o
-L<路径> 指定库文件搜索路径 gcc hello.c -L/usr/local/lib -lhello -o hello
-I<路径> 指定头文件搜索路径 gcc hello.c -I/usr/local/include -o hello
-l<库名> 链接指定的库(默认搜索 /lib/usr/lib gcc hello.c -lm -o hello(链接 libm.so 数学库)
-pthread 支持多线程编译 gcc hello.c -pthread -o hello
-std=<标准> 指定 C 或 C++ 标准 gcc -std=c99 hello.c -o hello
-D<宏定义> 定义宏,等效于 #define gcc -DDEBUG hello.c -o hello
-E 仅进行 预处理 ,输出 .i 文件 gcc -E hello.c -o hello.i
-S 仅进行 编译 ,输出汇编代码 .s 文件 gcc -S hello.c -o hello.s
-v 显示详细的编译过程 gcc -v hello.c -o hello
--version 显示 gcc/g++ 版本 gcc --version

G++ 额外的选项

选项 作用 示例
-fno-rtti 禁用运行时类型识别(RTTI) g++ -fno-rtti hello.cpp -o hello
-fno-exceptions 禁用异常处理(try/catch g++ -fno-exceptions hello.cpp -o hello
-std=c++11 使用 C++11 标准 g++ -std=c++11 hello.cpp -o hello
-std=c++17 使用 C++17 标准 g++ -std=c++17 hello.cpp -o hello
-std=c++20 使用 C++20 标准 g++ -std=c++20 hello.cpp -o hello

gccg++ 的关系

  • g++ 可以使用 gcc 的全部选项 ,因为 g++ 本质上是 gcc 的一个前端,专门用于编译 C++ 代码。

  • gcc 既可以编译 C 也可以编译 C++,但默认 不会自动链接 C++ 标准库

  • g++ 默认 会自动链接 C++ 标准库 (如 libstdc++),并开启 C++ 语法支持。

共勉


相关推荐
独行soc3 小时前
2025年渗透测试面试题总结-网络安全、Web安全、渗透测试笔试总结(一)(附回答)(题目+回答)
linux·运维·服务器·安全·web安全·面试·职场和发展
前进的程序员3 小时前
Linux 驱动开发步骤及 SPI 设备驱动移植示例
linux·运维·驱动开发
silenci3 小时前
vscode配置vim
vscode·vim·excel
cocogogogo4 小时前
配置Jupyter Notebook环境及Token认证(Linux服务器)
linux·服务器·jupyter
木心5 小时前
Linux如何安装AppImage程序
linux
今天阳光明媚吗5 小时前
Linux线程
linux
小跌—6 小时前
Linux:认识基础IO
linux·运维·数据库
Eric.Lee20216 小时前
ubuntu 挂载硬盘
linux·运维·ubuntu
xinruoqianqiu6 小时前
shell脚本--2
linux·运维·开发语言·前端·c++·chrome