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++ 语法支持。

共勉


相关推荐
许白掰43 分钟前
Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
linux·运维·服务器·前端·学习·编辑器
longze_75 小时前
Ubuntu连接不上网络问题(Network is unreachable)
linux·服务器·ubuntu
Dirschs5 小时前
【Ubuntu22.04安装ROS Noetic】
linux·ubuntu·ros
qianshanxue115 小时前
ubuntu 操作记录
linux
AmosTian8 小时前
【系统与工具】Linux——Linux简介、安装、简单使用
linux·运维·服务器
这我可不懂10 小时前
Python 项目快速部署到 Linux 服务器基础教程
linux·服务器·python
车车不吃香菇11 小时前
java idea 本地debug linux服务
java·linux·intellij-idea
tan77º11 小时前
【Linux网络编程】Socket - TCP
linux·网络·c++·tcp/ip
kfepiza12 小时前
Linux的`if test`和`if [ ]中括号`的取反语法比较 笔记250709
linux·服务器·笔记·bash
CodeWithMe12 小时前
【Note】《深入理解Linux内核》 第十九章:深入理解 Linux 进程通信机制
linux·运维·php