Linux网络编程:(八)GCC/G++ 编译器完全指南:从编译原理到实战优化,手把手教你玩转 C/C++ 编译

目录

前言

[一、认识 GCC/G++:编译器界的 "全能选手"](#一、认识 GCC/G++:编译器界的 “全能选手”)

[1.1 检查与安装 GCC/G++](#1.1 检查与安装 GCC/G++)

[1.1.1 检查是否已安装](#1.1.1 检查是否已安装)

[输出结果示例(Ubuntu 20.04):](#输出结果示例(Ubuntu 20.04):)

[1.1.2 安装 GCC/G++(CentOS 系统)](#1.1.2 安装 GCC/G++(CentOS 系统))

输出结果示例:

[1.1.3 安装 GCC/G++(Ubuntu 系统)](#1.1.3 安装 GCC/G++(Ubuntu 系统))

输出结果示例:

[1.2 GCC/G++ 的核心能力](#1.2 GCC/G++ 的核心能力)

[二、编译四步曲:从源代码到可执行文件的 "变身之旅"](#二、编译四步曲:从源代码到可执行文件的 “变身之旅”)

[2.1 准备示例代码](#2.1 准备示例代码)

[2.2 第一步:预处理(Preprocessing)------"整理" 源代码](#2.2 第一步:预处理(Preprocessing)——“整理” 源代码)

[2.2.1 预处理命令](#2.2.1 预处理命令)

[2.2.2 预处理做了什么?](#2.2.2 预处理做了什么?)

[2.2.3 为什么需要预处理?](#2.2.3 为什么需要预处理?)

[2.3 第二步:编译(Compilation)------"翻译" 成汇编语言](#2.3 第二步:编译(Compilation)——“翻译” 成汇编语言)

[2.3.1 编译命令](#2.3.1 编译命令)

[2.3.2 编译输出结果](#2.3.2 编译输出结果)

[2.3.3 语法错误示例](#2.3.3 语法错误示例)

[2.4 第三步:汇编(Assembly)------"翻译" 成机器码](#2.4 第三步:汇编(Assembly)——“翻译” 成机器码)

[2.4.1 汇编命令](#2.4.1 汇编命令)

[2.4.2 查看目标文件信息](#2.4.2 查看目标文件信息)

[2.5 第四步:链接(Linking)------"组装" 成可执行文件](#2.5 第四步:链接(Linking)——“组装” 成可执行文件)

[2.5.1 链接命令](#2.5.1 链接命令)

[2.5.2 运行可执行文件](#2.5.2 运行可执行文件)

[2.5.3 链接的核心:解决 "依赖" 问题](#2.5.3 链接的核心:解决 “依赖” 问题)

[2.6 一键编译:跳过中间文件](#2.6 一键编译:跳过中间文件)

[三、常用编译选项:让 GCC/G++"听你的话"](#三、常用编译选项:让 GCC/G++“听你的话”)

[3.1 基础选项:控制输出与阶段](#3.1 基础选项:控制输出与阶段)

示例:用-v查看编译细节

[3.2 调试选项:生成调试信息(配合 GDB)](#3.2 调试选项:生成调试信息(配合 GDB))

示例:生成调试信息并验证

[3.3 优化选项:平衡程序性能与编译速度](#3.3 优化选项:平衡程序性能与编译速度)

示例:对比不同优化选项的效果

[3.4 警告选项:提前发现代码隐患](#3.4 警告选项:提前发现代码隐患)

示例:用-Wall检测潜在问题

[3.5 头文件与库文件选项:解决 "找不到" 问题](#3.5 头文件与库文件选项:解决 “找不到” 问题)

[3.5.1 头文件路径选项-I(大写 i)](#3.5.1 头文件路径选项-I(大写 i))

[3.5.2 库文件路径与链接选项](#3.5.2 库文件路径与链接选项)

[示例 1:链接系统库(数学库libm.so)](#示例 1:链接系统库(数学库libm.so))

[示例 2:链接自定义库](#示例 2:链接自定义库)

[四、静态链接与动态链接:程序 "依赖" 的两种方式](#四、静态链接与动态链接:程序 “依赖” 的两种方式)

[4.1 静态链接:"自给自足" 的程序](#4.1 静态链接:“自给自足” 的程序)

[4.1.1 静态链接的特点](#4.1.1 静态链接的特点)

优点:

[4.1.2 静态链接实战](#4.1.2 静态链接实战)

[4.1.3 对比静态与动态程序](#4.1.3 对比静态与动态程序)

[4.2 动态链接:"按需加载" 的程序](#4.2 动态链接:“按需加载” 的程序)

[4.2.1 动态链接的特点](#4.2.1 动态链接的特点)

[4.2.2 动态链接的两种库文件](#4.2.2 动态链接的两种库文件)

[4.2.3 动态链接实战:自定义动态库](#4.2.3 动态链接实战:自定义动态库)

[4.3 静态链接 vs 动态链接:如何选择?](#4.3 静态链接 vs 动态链接:如何选择?)

[五、GCC/G++ 实战:多文件编译与常见问题解决](#五、GCC/G++ 实战:多文件编译与常见问题解决)

[5.1 多文件编译实战:学生成绩管理程序](#5.1 多文件编译实战:学生成绩管理程序)

[5.1.1 步骤 1:创建文件](#5.1.1 步骤 1:创建文件)

[5.1.2 步骤 2:多文件编译](#5.1.2 步骤 2:多文件编译)

[方式 1:直接指定所有源文件(简单快捷)](#方式 1:直接指定所有源文件(简单快捷))

[方式 2:先编译目标文件,再链接(适合大型项目)](#方式 2:先编译目标文件,再链接(适合大型项目))

[5.2 常见编译问题及解决方案](#5.2 常见编译问题及解决方案)

[5.2.1 问题 1:头文件找不到(No such file or directory)](#5.2.1 问题 1:头文件找不到(No such file or directory))

[5.2.2 问题 2:未定义引用(undefined reference to)](#5.2.2 问题 2:未定义引用(undefined reference to))

[5.2.3 问题 3:重复定义(multiple definition of)](#5.2.3 问题 3:重复定义(multiple definition of))

总结


前言

在 Linux 开发领域,GCC(GNU Compiler Collection)堪称 "编译器之王"------ 它不仅是 C/C++ 程序的核心编译工具,更是支撑 Linux 生态中无数开源项目的基石。无论是编写一个简单的 "Hello World" 程序,还是构建 Linux 内核这样的超大型项目,GCC 都以其强大的兼容性、丰富的优化选项和跨平台特性,成为开发者的首选工具。而 G++ 作为GCC家族中针对 C++ 的专用编译器,完美继承了 GCC 的核心能力,同时对 C++ 标准(从 C++98 到 C++20)提供了全面支持。

本文将从 "原理 + 实战" 双视角出发,带你彻底掌握 GCC/G++ 编译器:从编译的四个核心阶段(预处理、编译、汇编、链接)讲起,到常用编译选项的灵活运用,再到静态链接与动态链接的底层差异,最后结合实际案例讲解优化技巧与调试配置,让你不仅 "知其然",更 "知其所以然"。下面就让我我们正式开始吧!


一、认识 GCC/G++:编译器界的 "全能选手"

在开始实操前,我们先搞清楚一个常见疑问:GCC 和 G++ 到底是什么关系?简单来说,GCC 是一个编译器套件,支持 C、C++、Java、Fortran 等多种语言;而 G++ 是 GCC 套件中专门用于编译 C++ 程序的前端工具,本质上是对 GCC 的**"封装"**------ 当你用 G++ 编译代码时,它会自动调用 GCC 的核心编译能力,并默认链接 C++ 标准库(这是它与 GCC 编译 C 程序的关键区别)。

1.1 检查与安装 GCC/G++

Linux 系统( CentOS、Ubuntu等)通常预装了 GCC,但版本可能较旧。我们先检查当前版本,再根据需求安装或升级。

1.1.1 检查是否已安装

打开终端,执行以下命令查看 GCC/G++ 版本:

bash 复制代码
# 检查GCC版本(支持C编译)
gcc --version

# 检查G++版本(支持C++编译)
g++ --version
输出结果示例(Ubuntu 20.04)
复制代码
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

如果终端提示 "command not found",说明未安装,需执行以下命令安装。

1.1.2 安装 GCC/G++(CentOS 系统)

CentOS 使用**yum包管理器**,安装命令如下:

bash 复制代码
# 安装GCC(C编译器)和G++(C++编译器)
sudo yum install -y gcc gcc-c++
输出结果示例
复制代码
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
Resolving Dependencies
--> Running transaction check
---> Package gcc.x86_64 0:4.8.5-44.el7 will be installed
---> Package gcc-c++.x86_64 0:4.8.5-44.el7 will be installed
--> Processing Dependency: libstdc++-devel = 4.8.5-44.el7 for package: gcc-c++-4.8.5-44.el7.x86_64
--> Processing Dependency: libmpfr.so.4()(64bit) for package: gcc-4.8.5-44.el7.x86_64
--> Running transaction check
---> Package libmpfr.x86_64 0:3.1.1-4.el7 will be installed
---> Package libstdc++-devel.x86_64 0:4.8.5-44.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package              Arch         Version               Repository     Size
================================================================================
Installing:
 gcc                  x86_64       4.8.5-44.el7          base          16 M
 gcc-c++              x86_64       4.8.5-44.el7          base         7.2 M
Installing for dependencies:
 libmpfr              x86_64       3.1.1-4.el7           base         203 k
 libstdc++-devel      x86_64       4.8.5-44.el7          base         1.5 M

Transaction Summary
================================================================================
Install  2 Packages (+2 Dependent packages)

Total download size: 25 M
Installed size: 85 M
Downloading packages:
(1/4): libmpfr-3.1.1-4.el7.x86_64.rpm                       | 203 kB  00:00:00
(2/4): libstdc++-devel-4.8.5-44.el7.x86_64.rpm               | 1.5 MB  00:00:00
(3/4): gcc-c++-4.8.5-44.el7.x86_64.rpm                       | 7.2 MB  00:00:01
(4/4): gcc-4.8.5-44.el7.x86_64.rpm                           | 16 MB   00:00:02
--------------------------------------------------------------------------------
Total                                              9.8 MB/s | 25 MB  00:00:02
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : libmpfr-3.1.1-4.el7.x86_64                                   1/4
  Installing : gcc-4.8.5-44.el7.x86_64                                     2/4
  Installing : libstdc++-devel-4.8.5-44.el7.x86_64                           3/4
  Installing : gcc-c++-4.8.5-44.el7.x86_64                                   4/4
  Verifying  : libmpfr-3.1.1-4.el7.x86_64                                   1/4
  Verifying  : libstdc++-devel-4.8.5-44.el7.x86_64                           2/4
  Verifying  : gcc-4.8.5-44.el7.x86_64                                     3/4
  Verifying  : gcc-c++-4.8.5-44.el7.x86_64                                   4/4

Installed:
  gcc.x86_64 0:4.8.5-44.el7          gcc-c++.x86_64 0:4.8.5-44.el7

Dependency Installed:
  libmpfr.x86_64 0:3.1.1-4.el7       libstdc++-devel.x86_64 0:4.8.5-44.el7

Complete!

1.1.3 安装 GCC/G++(Ubuntu 系统)

Ubuntu 使用**apt包管理器**,安装命令更简洁:

bash 复制代码
# 更新软件源(可选,确保安装最新版本)
sudo apt update

# 安装GCC和G++
sudo apt install -y gcc g++
输出结果示例
复制代码
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  binutils binutils-common binutils-x86-64-linux-gnu cpp cpp-9 gcc-9 gcc-9-base
  libasan5 libatomic1 libbinutils libc6-dev libcc1-0 libcrypt-dev libctf-nobfd0
  libctf0 libexpat1-dev libfakeroot libfile-fcntllock-perl libgcc-9-dev
  libgomp1 libisl22 libitm1 liblsan0 libmpc3 libmpfr6 libmpx2 libnsl-dev
  libquadmath0 libstdc++6 libtsan0 libubsan1 libxau-dev libxdmcp-dev linux-libc-dev
  manpages manpages-dev
Suggested packages:
  binutils-doc cpp-doc gcc-9-locales gcc-multilib make manpages-posix
  manpages-posix-dev glibc-doc libstdc++-9-doc
The following NEW packages will be installed:
  binutils binutils-common binutils-x86-64-linux-gnu cpp cpp-9 gcc gcc-9
  gcc-9-base g++ libasan5 libatomic1 libbinutils libc6-dev libcc1-0 libcrypt-dev
  libctf-nobfd0 libctf0 libexpat1-dev libfakeroot libfile-fcntllock-perl
  libgcc-9-dev libgomp1 libisl22 libitm1 liblsan0 libmpc3 libmpfr6 libmpx2
  libnsl-dev libquadmath0 libstdc++6 libtsan0 libubsan1 libxau-dev libxdmcp-dev
  linux-libc-dev manpages manpages-dev
0 upgraded, 39 newly installed, 0 to remove and 0 not upgraded.
Need to get 49.7 MB of archives.
After this operation, 201 MB of additional disk space will be used.
Get:1 http://mirrors.aliyun.com/ubuntu focal/main amd64 gcc-9-base amd64 9.4.0-1ubuntu1~20.04.1 [20.2 kB]
Get:2 http://mirrors.aliyun.com/ubuntu focal/main amd64 libstdc++6 amd64 10.3.0-1ubuntu1~20.04 [515 kB]
...(中间省略部分下载过程)
Setting up gcc (4:9.3.0-1ubuntu2) ...
Setting up g++ (4:9.3.0-1ubuntu2) ...
Processing triggers for man-db (2.9.1-1) ...

安装完成后,再次执行gcc --versiong++ --version,确认版本正确即可。

1.2 GCC/G++ 的核心能力

为什么 GCC 能成为 Linux 开发的 "标配"?因为它具备以下三大核心优势:

  1. 多语言支持:除了 C/C++,还支持 Java、Python、Go 等数十种语言,一套工具搞定多语言开发。
  2. 跨平台编译:可以为不同架构(x86、ARM、RISC-V)和系统(Linux、Windows、macOS)生成可执行文件,比如在 x86 Linux 上编译 ARM 嵌入式程序。
  3. 强大的优化与调试:提供从 O0(无优化)到 O3(最高优化)的多级优化选项,同时支持生成调试信息(配合 GDB 调试),兼顾开发效率与程序性能。

二、编译四步曲:从源代码到可执行文件的 "变身之旅"

很多初学者会以为 "编译" 是一步完成的 ------ 输入gcc hello.c -o hello,按下回车就得到可执行文件。但实际上,这个命令背后隐藏了四个关键阶段预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)、链接(Linking)。理解这四个阶段,是掌握 GCC 高级用法的基础。

我们以一个简单的 C 程序hello.c为例,逐步拆解每个阶段的作用和输出结果。

2.1 准备示例代码

先创建一个hello.c文件,包含宏定义、头文件引用和主函数:

cpp 复制代码
#include <stdio.h>  // 引入标准输入输出头文件
#define NAME "Linux"  // 定义宏NAME

int main() {
    // 使用宏和printf函数
    printf("Hello, %s! This is GCC compiler.\n", NAME);
    return 0;
}

2.2 第一步:预处理(Preprocessing)------"整理" 源代码

预处理阶段的核心任务是:处理源代码中的 "特殊指令"#开头的指令,如#include#define#if等,大家如果忘了这部分内容,可以去看看我在C语言篇写过的预处理相关的内容),生成预处理后的代码文件(后缀为**.i**)。

2.2.1 预处理命令

使用**-E选项触发预处理,-o**指定输出文件(若不指定,会直接输出到终端):

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

2.2.2 预处理做了什么?

打开hello.i文件(约 1300 行,这里只看关键部分),你会发现三个变化:

  1. 头文件展开#include <stdio.h>被替换成stdio.h头文件的所有内容(包括printf函数的声明)。
  2. 宏替换 :**#define NAME "Linux"**被替换 ------所有NAME都变成了"Linux" ,比如printf中的%s对应的值从NAME变成了"Linux"
  3. 删除注释 :源代码中的**// 使用宏和printf函数被直接删除**,避免注释影响编译。

hello.i关键内容示例

cpp 复制代码
// (前面省略1200多行stdio.h的内容)
extern int fprintf (FILE *__restrict __stream, const char *__restrict __format, ...);
extern int printf (const char *__restrict __format, ...);  // stdio.h中printf的声明
// (中间省略部分内容)

int main() {
    // 注释被删除,宏NAME被替换为"Linux"
    printf("Hello, %s! This is GCC compiler.\n", "Linux");
    return 0;
}

2.2.3 为什么需要预处理?

因为编译器(后续的编译阶段)只认识 "纯 C 代码" ,不认识#include这类指令。预处理相当于**"翻译官"** ,把带有特殊指令的源代码,转换成编译器能理解的**"纯净代码"**。

2.3 第二步:编译(Compilation)------"翻译" 成汇编语言

编译阶段的核心任务是:将预处理后的.i文件(C 代码)翻译成汇编语言代码 (后缀为.s),同时进行语法检查 ------ 如果代码有语法错误(如少写分号、变量未定义),会在这个阶段报错。

2.3.1 编译命令

使用**-S**选项触发编译(注意是大写的 S),生成.s汇编文件:

bash 复制代码
gcc -S hello.i -o hello.s

2.3.2 编译输出结果

打开hello.s文件,会看到类似下面的汇编代码(不同架构的汇编指令略有差异,这里是 x86_64 架构):

cpp 复制代码
    .file   "hello.c"
    .text
    .section    .rodata
.LC0:
    .string "Hello, %s! This is GCC compiler."
    .string "Linux"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    $.LC1, %esi  # 将"Linux"的地址存入esi寄存器
    movl    $.LC0, %edi  # 将字符串常量的地址存入edi寄存器
    movl    $0, %eax
    call    printf       # 调用printf函数
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"
    .section    .note.GNU-stack,"",@progbits

这段代码的核心是:将main函数的逻辑翻译成汇编指令,比如**movl(移动数据)、call printf(调用 printf 函数)**。

2.3.3 语法错误示例

如果我们故意把hello.c中的printf写成print(少个f),再执行编译命令:

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

会看到编译报错,直接终止编译过程:

cpp 复制代码
hello.c: In function 'main':
hello.c:6:5: warning: implicit declaration of function 'print' [-Wimplicit-function-declaration]
     print("Hello, %s! This is GCC compiler.\n", NAME);
     ^~~~~
hello.c:6:5: error: incompatible implicit declaration of built-in function 'print'
hello.c:6:5: note: include '<stdio.h>' or provide a declaration of 'print'

这说明编译阶段会严格检查代码语法,只有语法正确的代码才能进入下一阶段。

2.4 第三步:汇编(Assembly)------"翻译" 成机器码

汇编阶段的核心任务是:将汇编语言.s文件翻译成机器能识别的二进制目标文件 (后缀为**.o**),这个文件包含了 CPU 可执行的指令,但还不能直接运行(因为缺少依赖的库函数,如printf的实现)。

2.4.1 汇编命令

使用**-c选项触发汇编,生成.o**目标文件:

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

2.4.2 查看目标文件信息

.o文件是二进制文件,直接打开会看到乱码,我们可以用file命令查看它的属性:

bash 复制代码
file hello.o

输出结果

复制代码
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

解释一下关键信息:

  • ELF 64-bit:表示是 64 位 ELF 格式文件(Linux 下可执行文件和目标文件的标准格式)。
  • relocatable:表示是 "可重定位目标文件"------ 意味着它还需要和其他目标文件或库文件链接,才能生成可执行文件。

2.5 第四步:链接(Linking)------"组装" 成可执行文件

链接阶段是最后一步,核心任务是:将目标文件(.o)与依赖的库文件(如 C 标准库libc.so)合并,生成可执行文件

2.5.1 链接命令

直接使用gcc命令,输入目标文件,指定输出可执行文件名称:

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

2.5.2 运行可执行文件

执行生成的hello文件,验证结果:

bash 复制代码
./hello

输出结果

复制代码
Hello, Linux! This is GCC compiler.

成功运行!这说明四个阶段全部完成,源代码最终变成了可执行程序。

2.5.3 链接的核心:解决 "依赖" 问题

为什么需要链接?因为我们的代码中使用了printf函数,但hello.o中只有printf调用指令(call printf,没有printf的实现代码 ------printf的实现放在系统的 C 标准库(libc.so)中。链接阶段的作用就是:找到printf的实现代码,并将其 "拼接" 到我们的可执行文件中(或者记录库文件的位置,运行时动态加载)。

我们可以用ldd命令查看可执行文件依赖的库:

bash 复制代码
ldd hello

输出结果

复制代码
linux-vdso.so.1 (0x00007ffd7b7f7000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8b3a800000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8b3aa1a000)

其中libc.so.6就是 C 标准库ld-linux-x86-64.so.2动态链接器(负责运行时加载库文件)。

2.6 一键编译:跳过中间文件

在实际开发中,我们很少单独执行四个阶段的命令,而是用一条命令直接生成可执行文件:

bash 复制代码
# 编译C程序
gcc hello.c -o hello

# 编译C++程序(将gcc换成g++,源文件后缀为.cpp)
g++ hello.cpp -o hello_cpp

这条命令会自动完成**"预处理→编译→汇编→链接"四个阶段,中间生成的.i、.s、.o**文件会被自动删除,只保留最终的可执行文件。

三、常用编译选项:让 GCC/G++"听你的话"

GCC 提供了数百个编译选项,但常用的只有十几个。掌握这些选项,能让你灵活控制编译过程 ------ 比如生成调试信息、开启优化、指定头文件路径等。我们按 "功能分类" 讲解最实用的选项,每个选项都搭配示例。

3.1 基础选项:控制输出与阶段

这类选项用于指定输出文件、控制编译阶段,是最常用的 "入门级" 选项。

选项 功能描述 示例
-o <file> 指定输出文件名称(可用于中间文件或可执行文件) gcc hello.c -o hello(生成可执行文件 hello)
-E 只执行预处理,生成.i文件 gcc -E hello.c -o hello.i
-S 执行预处理 + 编译,生成.s汇编文件 gcc -S hello.c -o hello.s
-c 执行预处理 + 编译 + 汇编,生成.o目标文件 gcc -c hello.c -o hello.o
-v 显示编译过程的详细信息(包括调用的工具、参数) gcc -v hello.c -o hello

示例:用-v查看编译细节

执行gcc -v hello.c -o hello,会输出大量信息,其中关键部分是 "调用的工具链" 和 "搜索路径":

复制代码
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ...(省略配置信息)
Thread model: posix
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
COLLECT_GCC_OPTIONS='-v' '-o' 'hello' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/9/cc1 -quiet -v -imultiarch x86_64-linux-gnu hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello -version -fstack-protector-strong -Wformat -Wformat-security -o /tmp/ccX7Zk8G.s
GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.1) version 9.4.0 (x86_64-linux-gnu)
	compiled by GNU C version 9.4.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP

warning: GMP header version 6.2.0 differs from library version 6.2.1.
warning: MPFR header version 4.0.2 differs from library version 4.0.3.
warning: MPC header version 1.1.0 differs from library version 1.2.1.
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring duplicate directory "/usr/local/include/x86_64-linux-gnu"
ignoring duplicate directory "/usr/lib/gcc/x86_64-linux-gnu/9/include"
...(省略头文件搜索路径)
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-linux-gnu/9/include
 /usr/local/include
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.
GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.1) version 9.4.0 (x86_64-linux-gnu)
	compiled by GNU C version 9.4.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP
...(省略后续汇编和链接步骤)

通过**-v选项,你可以看到 GCC 在预处理阶段搜索头文件的路径(如/usr/include)、调用的编译器(cc1)和汇编器(as)**,这对解决 "头文件找不到" 等问题非常有用。

3.2 调试选项:生成调试信息(配合 GDB)

如果需要用 GDB 调试程序(后续我会详细介绍GDB),必须在编译时生成调试信息(记录变量、行号等信息),否则 GDB 无法定位代码。核心选项是-g

选项 功能描述 示例
-g 生成调试信息(默认包含行号、变量信息,配合 GDB 使用) gcc -g hello.c -o hello
-g3 生成更详细的调试信息(包括宏定义、注释等) gcc -g3 hello.c -o hello
-ggdb 生成 GDB 专用的调试信息(优化调试体验) gcc -ggdb hello.c -o hello

示例:生成调试信息并验证

(1)编译时添加**-g**选项:

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

(2)用file命令查看调试信息是否生成:

bash 复制代码
file hello_debug

输出结果

bash 复制代码
hello_debug: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0, with debug_info, not stripped

其中with debug_info表示包含调试信息,not stripped表示调试信息未被剥离(strip命令会删除调试信息,减小文件体积)。

(3)用 GDB 验证调试功能:

bash 复制代码
gdb hello_debug

进入 GDB 后,输入list main查看main函数的代码(如果没有调试信息,会提示 "No line number information available"):

cpp 复制代码
(gdb) list main
1	#include <stdio.h>
2	#define NAME "Linux"
3
4	int main() {
5	    printf("Hello, %s! This is GCC compiler.\n", NAME);
6	    return 0;
7	}

能看到行号和代码,说明调试信息生成成功。

3.3 优化选项:平衡程序性能与编译速度

GCC 提供了从-O0-O3的四级优化选项,还有-Os(优化代码体积),不同选项对应不同的优化策略。

选项 功能描述 适用场景
-O0 无优化(默认选项) 开发阶段,编译速度快,便于调试(变量值不会被优化)
-O1(或-O) 基础优化(优化代码大小和执行速度,不增加编译时间) 日常开发,兼顾性能和编译速度
-O2 中级优化(比-O1更全面,如循环展开、函数内联 生产环境,追求更高性能,编译时间适中
-O3 高级优化(在-O2基础上增加向量优化、循环优化等) 对性能要求极高的场景(如科学计算),编译时间长
-Os 优化代码体积(减少可执行文件大小,适合嵌入式设备 存储空间有限的场景(如 ARM 嵌入式程序)

示例:对比不同优化选项的效果

我们用一个计算 1 到 1000000 求和的程序sum.c,测试不同优化选项对程序体积和运行时间的影响:

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

int main() {
    long long sum = 0;
    for (int i = 1; i <= 1000000; i++) {
        sum += i;
    }
    printf("Sum: %lld\n", sum);
    return 0;
}
  1. 用不同优化选项编译:

    bash 复制代码
    # O0(无优化)
    gcc -O0 sum.c -o sum_O0
    
    # O2(中级优化)
    gcc -O2 sum.c -o sum_O2
    
    # O3(高级优化)
    gcc -O3 sum.c -o sum_O3
    
    # Os(优化体积)
    gcc -Os sum.c -o sum_Os
  2. 对比文件大小(用ls -l命令):

    bash 复制代码
    ls -l sum_O0 sum_O2 sum_O3 sum_Os

输出结果

复制代码
-rwxrwxr-x 1 user user 16824 11月 15 10:00 sum_O0
-rwxrwxr-x 1 user user 16304 11月 15 10:00 sum_O2
-rwxrwxr-x 1 user user 16304 11月 15 10:00 sum_O3
-rwxrwxr-x 1 user user 16280 11月 15 10:00 sum_Os

我们可以看到:

  • sum_O0(无优化)体积最大(16824 字节)。
  • sum_Os(优化体积)体积最小(16280 字节)。
  • O2O3体积相近,略小于O0
  1. 对比运行时间(用time命令):

    bash 复制代码
    # 测试O0版本
    time ./sum_O0
    
    # 测试O2版本
    time ./sum_O2

O0 版本输出

复制代码
Sum: 500000500000

real	0m0.003s
user	0m0.000s
sys	0m0.003s

O2 版本输出

复制代码
Sum: 500000500000

real	0m0.001s
user	0m0.001s
sys	0m0.000s

虽然程序简单,优化效果不明显,但仍能看出**O2版本的运行时间更** 短 ------ 因为编译器对循环做了优化(如循环展开、变量缓存)。对于复杂程序,O2O3的优化效果会更显著。

3.4 警告选项:提前发现代码隐患

GCC 的警告功能非常强大,能检测出代码中的潜在问题(如未初始化变量、类型不匹配、无用变量等)。建议大家始终开启警告选项,避免 "隐性 bug"。

选项 功能描述 示例
-Wall 开启所有常见警告(推荐必加) gcc -Wall hello.c -o hello
-Wextra -Wall基础上,开启更多警告(如未使用的参数) gcc -Wall -Wextra hello.c -o hello
-Werror 将警告视为错误(强制修复所有警告才能编译通过) gcc -Wall -Werror hello.c -o hello
-Wunused 检测未使用的变量、函数(如定义了变量但未使用) gcc -Wall -Wunused hello.c -o hello

示例:用-Wall检测潜在问题

我们故意写一个有隐患的代码warn.c

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

// 定义了函数但未使用
int unused_func() {
    return 100;
}

int main() {
    int a;  // 定义了变量但未初始化
    printf("a = %d\n", a);  // 使用未初始化的变量
    return 0;
}

使用-Wall选项编译:

bash 复制代码
gcc -Wall warn.c -o warn

输出警告信息

bash 复制代码
warn.c:3:5: warning: 'unused_func' defined but not used [-Wunused-function]
 int unused_func() {
     ^~~~~~~~~~~
warn.c: In function 'main':
warn.c:8:6: warning: variable 'a' is used uninitialized in this function [-Wuninitialized]
     int a;
         ^
warn.c:9:22: note: 'a' was declared here
     printf("a = %d\n", a);
                      ^

这些警告提示了两个问题:

  1. unused_func函数定义了但未使用。
  2. 变量**a未初始化就被使用**(运行时可能输出随机值)。

如果加上**-Werror**选项,警告会变成错误,编译直接失败:

bash 复制代码
gcc -Wall -Werror warn.c -o warn

输出结果

bash 复制代码
warn.c:3:5: error: 'unused_func' defined but not used [-Werror=unused-function]
 int unused_func() {
     ^~~~~~~~~~~
warn.c: In function 'main':
warn.c:8:6: error: variable 'a' is used uninitialized in this function [-Werror=uninitialized]
     int a;
         ^
warn.c:9:22: note: 'a' was declared here
     printf("a = %d\n", a);
                      ^
cc1: all warnings being treated as errors

-Werror适合团队开发,强制所有人修复警告,保证代码质量。

3.5 头文件与库文件选项:解决 "找不到" 问题

当你的代码引用了**"非系统默认路径"**的头文件或库文件时(如自己写的头文件、第三方库),需要用以下选项指定路径,否则 GCC 会报错 "头文件找不到" 或 "库文件找不到"。

3.5.1 头文件路径选项-I(大写 i)

-I <path>:指定头文件搜索路径(GCC 会先搜索-I指定的路径,再搜索系统默认路径)。

示例 :假设我们有一个自定义头文件myheader.h,放在./include目录下,内容如下:

cpp 复制代码
// ./include/myheader.h
#define MAX_NUM 100
void print_max();  // 函数声明

对应的实现文件myfunc.c放在**./src**目录下:

cpp 复制代码
// ./src/myfunc.c
#include "myheader.h"
#include <stdio.h>

void print_max() {
    printf("Max number is: %d\n", MAX_NUM);
}

主程序main.c在当前目录,引用myheader.h

cpp 复制代码
// main.c
#include "myheader.h"

int main() {
    print_max();
    return 0;
}

如果直接编译,会报错**"myheader.h: No such file or directory",因为 GCC 默认只搜索/usr/include等系统路径,找不到./include**下的头文件。

正确的编译命令需要用**-I ./include**指定头文件路径,同时指定所有源文件:

bash 复制代码
gcc main.c ./src/myfunc.c -I ./include -o myprog

运行程序:

bash 复制代码
./myprog

输出结果

复制代码
Max number is: 100

3.5.2 库文件路径与链接选项

如果代码依赖第三方库(如数学库、网络库),需要用**-L指定库文件路径,-l**(小写 L)指定库名称。

示例 1:链接系统库(数学库libm.so

C 标准库中的数学函数(如sinsqrt)不在默认链接的libc.so中,而是在libm.so中,需要手动链接。

创建math_test.c

cpp 复制代码
#include <stdio.h>
#include <math.h>  // 包含数学库头文件

int main() {
    double x = 2.0;
    double result = sqrt(x);  // 使用sqrt函数(在libm.so中)
    printf("sqrt(%.1f) = %.2f\n", x, result);
    return 0;
}

直接编译会报错:

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

错误信息

bash 复制代码
/tmp/ccY6Zk7G.o: In function `main':
math_test.c:(.text+0x2a): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status

错误原因是 "找不到sqrt的引用"------ 因为没有链接libm.so库。正确的命令需要加-lm-l指定库名称m,GCC 会自动补全为libm.so):

复制代码
gcc math_test.c -o math_test -lm

运行程序:

bash 复制代码
./math_test

输出结果

复制代码
sqrt(2.0) = 1.41
示例 2:链接自定义库

如果我们将myfunc.c编译成静态库libmyfunc.a,再链接到主程序中,步骤如下:

  1. 编译myfunc.c生成目标文件

    bash 复制代码
    gcc -c ./src/myfunc.c -I ./include -o myfunc.o
  2. ar命令创建静态库ar是归档工具,用于打包目标文件):

    bash 复制代码
    ar rcs libmyfunc.a myfunc.o
    • r:替换库中的旧文件。
    • c:创建新库(若库不存在)。
    • s:生成库的索引(加快链接速度)。
  3. 链接静态库编译主程序:

    bash 复制代码
    # -L .:指定库文件路径为当前目录(libmyfunc.a在当前目录)
    # -lmyfunc:指定链接libmyfunc.a库(-l后加库名称myfunc)
    gcc main.c -o myprog_lib -I ./include -L . -lmyfunc
  4. 运行程序:

    bash 复制代码
    ./myprog_lib

输出结果

复制代码
Max number is: 100

四、静态链接与动态链接:程序 "依赖" 的两种方式

在链接阶段,GCC 支持两种链接方式:静态链接(Static Linking)动态链接(Dynamic Linking)。这两种方式的核心区别是 "库文件是否被打包到可执行文件中",直接影响程序的体积、运行效率和可移植性。

4.1 静态链接:"自给自足" 的程序

静态链接的原理是:将程序依赖的库文件(如libc.alibmyfunc.a)的代码 "完整复制" 到可执行文件中。生成的可执行文件不依赖外部库,可以单独运行。

4.1.1 静态链接的特点

优点

  1. 可移植性强:程序不依赖外部库,复制到其他相同架构的 Linux 系统中即可运行。
  2. 运行速度快:库代码已包含在程序中,无需运行时加载库文件。
  • 缺点
    1. 程序体积大:每个程序都包含一份库代码,若多个程序依赖同一个库,会浪费磁盘空间和内存。
    2. 更新麻烦:若库文件有 bug 修复或功能更新,需要重新编译链接程序才能生效。

4.1.2 静态链接实战

GCC 默认使用动态链接,要启用静态链接,需添加**-static**选项。

hello.c为例,静态链接 C 标准库:

bash 复制代码
# 静态链接,生成静态可执行文件
gcc -static hello.c -o hello_static

4.1.3 对比静态与动态程序

(1)对比文件大小:

bash 复制代码
# 动态链接版本(之前生成的hello)
gcc hello.c -o hello_dynamic

# 查看两个文件的大小
ls -l hello_static hello_dynamic

输出结果

bash 复制代码
-rwxrwxr-x 1 user user  16824 11月 15 11:00 hello_dynamic
-rwxrwxr-x 1 user user 846448 11月 15 11:01 hello_static

可以看到,静态链接hello_static体积(846KB)远大于动态链接hello_dynamic(16KB)------ 因为hello_static包含了 C 标准库的完整代码。

(2)查看依赖的库:

bash 复制代码
# 动态链接程序的依赖库
ldd hello_dynamic

# 静态链接程序无依赖库(ldd会提示不是动态可执行文件)
ldd hello_static

动态链接程序输出

复制代码
linux-vdso.so.1 (0x00007ffd7b7f7000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8b3a800000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8b3aa1a000)

静态链接程序输出

复制代码
	not a dynamic executable
  • 测试的可移植性:将hello_static复制到另一台未安装 C 标准库的 Linux 系统中,执行./hello_static,能正常输出 "Hello, Linux! ...";而hello_dynamic会报错 "error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory",因为找不到依赖的libc.so.6

4.2 动态链接:"按需加载" 的程序

动态链接的原理是:不将库代码复制到可执行文件中,而是在程序运行时,由动态链接器(ld-linux-x86-64.so.2)加载依赖的库文件。生成的可执行文件体积小,且多个程序可共享同一个库文件。

4.2.1 动态链接的特点

  • 优点
    1. 程序体积小:仅包含自身代码和库的 "引用信息",不包含库代码。
    2. 共享库资源:多个程序可共享同一个库文件,节省磁盘空间和内存(库文件只需加载一次到内存)。
    3. 更新方便:若库文件更新,无需重新编译程序,直接替换库文件即可(前提是接口兼容)。
  • 缺点
    1. 可移植性差:程序依赖外部库,若目标系统缺少对应的库文件,无法运行。
    2. 运行速度略慢:需要在运行时加载库文件,增加少量启动时间。

4.2.2 动态链接的两种库文件

Linux 下的动态库文件有两种后缀:

  1. libxxx.so动态共享库(Shared Object),是编译后的二进制文件,可直接被动态链接器加载
  2. libxxx.so.x.y.z版本化动态库(如libc.so.6x是主版本号(接口不兼容),y是次版本号(接口兼容,新增功能),z是修订号(bug 修复)。

4.2.3 动态链接实战:自定义动态库

我们将myfunc.c编译成动态库libmyfunc.so,再链接到主程序中。

(1)编译动态库:

bash 复制代码
# -fPIC:生成位置无关代码(Position Independent Code),动态库必须加此选项
# -shared:生成动态库
gcc -fPIC -shared ./src/myfunc.c -I ./include -o libmyfunc.so

(2)链接动态库编译主程序:

bash 复制代码
# -L .:指定动态库路径为当前目录
# -lmyfunc:链接libmyfunc.so动态库
gcc main.c -o myprog_dyn -I ./include -L . -lmyfunc

(3)运行程序:

直接运行会报错 "找不到动态库",因为动态链接器默认只搜索/lib/usr/lib等系统路径,找不到当前目录的libmyfunc.so

解决方法有三种:

  • 方法 1:将动态库复制到系统库路径(需要 root 权限):

    bash 复制代码
    sudo cp libmyfunc.so /usr/lib
    ./myprog_dyn
  • 方法 2:设置LD_LIBRARY_PATH环境变量(临时生效,重启终端后失效):

    bash 复制代码
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.  # 添加当前目录到库搜索路径
    ./myprog_dyn
  • 方法 3:修改/etc/ld.so.conf配置文件(永久生效):

    bash 复制代码
    sudo echo "./" >> /etc/ld.so.conf  # 添加当前目录到配置文件(实际开发中建议用绝对路径)
    sudo ldconfig  # 更新动态链接器缓存
    ./myprog_dyn

运行结果

复制代码
Max number is: 100
  • 查看程序依赖的动态库:

    bash 复制代码
    ldd myprog_dyn

输出结果

复制代码
linux-vdso.so.1 (0x00007ffd7b7f7000)
libmyfunc.so => ./libmyfunc.so (0x00007f8b3a7f0000)  # 依赖我们的动态库
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8b3a600000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8b3a81a000)

可以看到,程序成功依赖了libmyfunc.so动态库。

4.3 静态链接 vs 动态链接:如何选择?

场景 推荐链接方式 原因
嵌入式设备(存储空间有限) 动态链接 多个程序共享库文件,节省存储空间
独立部署的工具(如脚本解释器) 静态链接 无需依赖系统库,复制即可运行
企业内部服务(如后端 API) 动态链接 库更新时无需重新部署程序,降低维护成本
对性能要求极高的程序(如实时系统) 静态链接 避免运行时加载库的开销,提升响应速度

五、GCC/G++ 实战:多文件编译与常见问题解决

在实际开发中,我们很少写 "单文件程序",而是将代码拆分到多个.c/.cpp文件和头文件中。掌握多文件编译的方法,是应对中大型项目的基础。同时,我们还会讲解开发中常见的编译问题及解决方案。

5.1 多文件编译实战:学生成绩管理程序

我们以一个简单的 "学生成绩管理程序" 为例,包含 3 个文件:

  1. student.h:头文件,声明结构体和函数。
  2. student.c:源文件,实现成绩计算相关函数。
  3. main.c:主程序,调用student.c中的函数。

5.1.1 步骤 1:创建文件

(1)student.h(头文件):

cpp 复制代码
#ifndef STUDENT_H
#define STUDENT_H  // 防止头文件重复包含(头文件保护)

// 定义学生结构体
typedef struct {
    char name[50];
    int id;
    float scores[3];  // 3门课程成绩
    float average;    // 平均分
} Student;

// 函数声明:计算学生平均分
void calculate_average(Student *stu);

// 函数声明:打印学生信息
void print_student(Student *stu);

#endif  // STUDENT_H

注意 :头文件中必须加 "头文件保护"(#ifndef/#define/#endif),防止因多次#include导致结构体和函数重复声明。

(2)student.c(源文件):

cpp 复制代码
#include "student.h"
#include <stdio.h>

// 实现计算平均分的函数
void calculate_average(Student *stu) {
    float sum = 0;
    for (int i = 0; i < 3; i++) {
        sum += stu->scores[i];
    }
    stu->average = sum / 3;
}

// 实现打印学生信息的函数
void print_student(Student *stu) {
    printf("ID: %d\n", stu->id);
    printf("Name: %s\n", stu->name);
    printf("Scores: %.1f, %.1f, %.1f\n", 
           stu->scores[0], stu->scores[1], stu->scores[2]);
    printf("Average: %.1f\n", stu->average);
}

(3)main.c(主程序):

cpp 复制代码
#include "student.h"
#include <stdio.h>
#include <string.h>

int main() {
    // 定义一个学生变量并初始化
    Student stu;
    stu.id = 1001;
    strcpy(stu.name, "Zhang San");
    stu.scores[0] = 85.5;
    stu.scores[1] = 92.0;
    stu.scores[2] = 78.5;

    // 计算平均分
    calculate_average(&stu);

    // 打印学生信息
    print_student(&stu);

    return 0;
}

5.1.2 步骤 2:多文件编译

多文件编译有两种方式:直接指定所有源文件,或先编译成目标文件再链接。

方式 1:直接指定所有源文件(简单快捷)
bash 复制代码
gcc main.c student.c -o student_manage

运行程序:

bash 复制代码
./student_manage

输出结果

bash 复制代码
ID: 1001
Name: Zhang San
Scores: 85.5, 92.0, 78.5
Average: 85.3
方式 2:先编译目标文件,再链接(适合大型项目)

对于包含数十个源文件的项目,直接编译所有文件会很慢(修改一个文件需要重新编译所有文件)。更好的方式是:将每个源文件编译成目标文件(.o),再链接所有目标文件------ 修改一个文件时,只需重新编译对应的目标文件,节省时间。

bash 复制代码
# 1. 编译main.c生成main.o
gcc -c main.c -o main.o

# 2. 编译student.c生成student.o
gcc -c student.c -o student.o

# 3. 链接所有目标文件,生成可执行文件
gcc main.o student.o -o student_manage_obj

运行程序,结果与方式 1 一致:

bash 复制代码
./student_manage_obj

5.2 常见编译问题及解决方案

在多文件编译中,初学者常遇到 "头文件找不到""未定义引用""重复定义" 等问题,我们逐一讲解解决方案。

5.2.1 问题 1:头文件找不到(No such file or directory)

错误示例

bash 复制代码
gcc main.c student.c -o student_manage

错误信息

bash 复制代码
main.c:1:10: fatal error: student.h: No such file or directory
 #include "student.h"
          ^~~~~~~~~~~
compilation terminated.

原因student.h不在当前目录,或不在 GCC 的默认搜索路径中。

解决方案

  • 若头文件在./include目录,用**-I ./include**指定路径:

    bash 复制代码
    gcc main.c student.c -I ./include -o student_manage
  • 若头文件在其他路径,将路径替换为实际路径(如-I /home/user/project/include)。

5.2.2 问题 2:未定义引用(undefined reference to)

错误示例 :只编译main.c,未编译student.c

bash 复制代码
gcc main.c -o student_manage

错误信息

复制代码
/tmp/ccX7Zk8G.o: In function `main':
main.c:(.text+0x5a): undefined reference to `calculate_average'
main.c:(.text+0x66): undefined reference to `print_student'
collect2: error: ld returned 1 exit status

原因main.c中调用了calculate_averageprint_student函数,但这两个函数的实现在student.c中,未被编译链接

解决方案:编译时包含所有相关的源文件或目标文件:

bash 复制代码
# 包含student.c
gcc main.c student.c -o student_manage

# 或包含student.o(已提前编译)
gcc main.c student.o -o student_manage

5.2.3 问题 3:重复定义(multiple definition of)

错误示例 :在student.h中定义全局变量,然后在main.cstudent.c中都#include "student.h"

bash 复制代码
// student.h中错误定义全局变量
int global_var = 10;

编译时会报错:

bash 复制代码
gcc main.c student.c -o student_manage

错误信息

复制代码
/tmp/ccY6Zk7G.o:(.data+0x0): multiple definition of `global_var'
/tmp/ccX7Zk8G.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

原因 :头文件被多个源文件#include后,全局变量global_var会在每个源文件中被定义一次,链接时出现重复定义。

解决方案

  • 头文件中只声明 全局变量(用extern),不定义

    cpp 复制代码
    // student.h中声明全局变量
    extern int global_var;
  • 在一个源文件(如student.c)中定义 全局变量:

    cpp 复制代码
    // student.c中定义全局变量
    int global_var = 10;

总结

GCC/G++ 是 Linux 开发的 "基石工具",掌握它不仅能让你高效编译 C/C++ 程序,更能帮助你理解程序从源代码到可执行文件的底层逻辑。希望本文能成为你学习 GCC/G++ 的 "入门钥匙",后续可结合实际项目不断实践,逐步解锁更多高级用法!

相关推荐
阿乐艾官2 小时前
【十一、Linux管理网络安全】
linux·运维·web安全
大锦终2 小时前
【动规】背包问题
c++·算法·动态规划
LoneEon3 小时前
告别手动操作:用 Ansible 统一管理你的 Ubuntu 服务器集群
运维·服务器·ansible
犯困的土子哥3 小时前
C++:哈希表
c++·哈希算法
百***67033 小时前
Nginx搭建负载均衡
运维·nginx·负载均衡
Code Warrior3 小时前
【Linux】Socket 编程预备知识
linux·网络·c++
智者知已应修善业3 小时前
【c语言蓝桥杯计算卡片题】2023-2-12
c语言·c++·经验分享·笔记·算法·蓝桥杯
littlepeanut.top3 小时前
C++中将FlatBuffers序列化为JSON
开发语言·c++·json·flatbuffers
梁正雄3 小时前
17、grafana安装
运维·grafana·prometheus·监控