目录

一、gcc/g++ 编译器:代码到可执行文件的蜕变

1. 预处理:代码的 “初步加工”

2. 编译:生成汇编指令

3. 汇编:转化为机器码

4. 链接:生成可执行程序

5. 调试模式编译

二、Makefile:自动化构建的 “利器”

1. Makefile 核心原理

2. 基础 Makefile 编写

3. 进阶 Makefile:变量与通配符

三、第一个 Linux 程序:实现动态进度条

1. 核心知识点铺垫

2. 版本 1:基础进度条

(1)头文件process.h

(2)源文件process.c

(3)主程序main.c

(4)编写 Makefile

3. 版本 2:适配业务场景的进度条

(1)扩展头文件process.h

(2)扩展源文件process.c

(3)编写业务主程序main.c

四、总结


在 Linux 环境下进行 C/C++ 开发,掌握编译器工具、自动化构建工具以及基础程序编写能力是入门的关键。本文将从 gcc/g++ 编译器的编译流程、Makefile 的编写逻辑,到实现第一个 Linux 特色程序 —— 进度条,全方位拆解开发过程中的核心知识点。

一、gcc/g++ 编译器:代码到可执行文件的蜕变

gcc(GNU C Compiler)和 g++(GNU C++ Compiler)是 Linux 下最常用的 C/C++ 编译器,其核心工作是将源代码转化为可执行程序,整个过程分为预处理、编译、汇编、链接四个阶段。

1. 预处理:代码的 “初步加工”

预处理阶段主要完成宏替换、头文件展开、去注释、条件编译等操作,该阶段不会进行语法检查。

  • 核心指令gcc -E 源文件 -o 预处理文件.i(C 语言)、g++ -E 源文件 -o 预处理文件.i(C++ 语言)
  • 示例:对test.c进行预处理,生成test.i文件
    gcc -E test.c -o test.i
    
    预处理后,代码中的#include头文件会被完整展开,#define宏会被直接替换,注释则会被全部清除。

2. 编译:生成汇编指令

编译阶段会对预处理后的代码进行语法、语义分析,在确认无语法错误后,将其转化为汇编语言代码。

  • 核心指令gcc -S 预处理文件.i -o 汇编文件.s
  • 示例:将预处理后的test.i转化为汇编文件test.s
    gcc -S test.i -o test.s
    
    此时生成的.s文件为汇编代码,可直接被汇编器处理。

3. 汇编:转化为机器码

汇编阶段的任务是将汇编代码转化为机器可识别的二进制目标文件(.o文件),该文件为可重定位目标文件,无法直接执行。

  • 核心指令gcc -c 汇编文件.s -o 目标文件.o
  • 示例:将test.s转化为二进制目标文件test.o
    gcc -c test.s -o test.o
    

4. 链接:生成可执行程序

链接阶段是将多个目标文件以及系统库文件整合,生成最终的可执行程序。Linux 下链接分为静态链接动态链接

  • 动态链接(默认):程序运行时才加载依赖的动态库(.so文件),生成的可执行程序体积小,但运行时依赖系统动态库。
  • 静态链接:编译时将依赖的静态库(.a文件)代码直接拷贝到可执行程序中,程序可独立运行,但体积较大。
  • 核心指令
    • 动态链接:gcc 目标文件.o -o 可执行程序名
    • 静态链接:gcc 目标文件.o -o 可执行程序名 -static(需提前安装静态库,如 CentOS 下yum install glibc-static libstdc++-static -y
  • 示例:将test.o链接为可执行程序test
    gcc test.o -o test
    

5. 调试模式编译

默认情况下 gcc/g++ 生成的是 release 模式程序,无法调试。若需使用 gdb 调试,需添加-g参数生成 debug 模式程序:

gcc test.c -o test -g

二、Makefile:自动化构建的 “利器”

当项目中存在多个源文件时,手动执行编译指令会变得繁琐且易出错。Makefile 通过定义依赖关系编译规则,实现项目的自动化编译与清理,极大提升开发效率。

1. Makefile 核心原理

Makefile 的核心是 “依赖关系” 和 “依赖方法”:

  • 依赖关系:目标文件依赖哪些源文件(如可执行程序依赖目标文件,目标文件依赖源文件)。
  • 依赖方法:通过什么指令生成目标文件。
  • 此外,可通过.PHONY声明伪目标(如clean),伪目标的特性是 “总是会被执行”,不受文件时间戳影响。

2. 基础 Makefile 编写

以单文件项目test.c为例,编写 Makefile:

makefile

# 定义可执行程序名
test:test.o
	gcc test.o -o test
# 目标文件依赖源文件
test.o:test.c
	gcc -c test.c -o test.o
# 声明clean为伪目标
.PHONY:clean
# 清理编译产物
clean:
	rm -f test.o test

执行make命令即可自动编译生成test程序,执行make clean可清理编译产物。

3. 进阶 Makefile:变量与通配符

对于多文件项目,可通过变量通配符简化 Makefile 编写,提升通用性:

makefile

# 定义变量:编译器、源文件、目标文件、可执行程序名
CC=gcc
SRC=$(wildcard *.c)  # 获取当前目录所有.c文件
OBJ=$(SRC:.c=.o)     # 将所有.c文件替换为.o文件
BIN=app

# 生成可执行程序
$(BIN):$(OBJ)
	$(CC) -o $@ $^  # $@代表目标文件名,$^代表所有依赖文件
# 生成目标文件
%.o:%.c
	$(CC) -c $< -o $@  # $<代表依赖列表中的第一个文件
# 清理规则
.PHONY:clean
clean:
	rm -f $(OBJ) $(BIN)

此 Makefile 可适配任意多.c文件的项目,无需逐个修改编译指令。

三、第一个 Linux 程序:实现动态进度条

进度条是 Linux 下典型的交互式程序,其核心是利用行缓冲区光标控制实现动态刷新效果,下面分步实现两种版本的进度条。

1. 核心知识点铺垫

  • 回车与换行\n是换行(光标移到下一行开头),\r是回车(光标回到当前行开头,不换行),进度条的动态刷新依赖\r
  • 行缓冲区:标准输出默认是行缓冲,只有遇到\n或手动刷新(fflush(stdout))时,内容才会输出到终端。
  • usleep 函数:用于控制进度条刷新速度(单位为微秒,1 秒 = 1000000 微秒)。

2. 版本 1:基础进度条

实现一个从 0% 到 100% 的基础进度条,包含进度条样式、百分比和动态旋转光标:

(1)头文件process.h
#pragma once
#include <stdio.h>
void process_v1();
(2)源文件process.c
#include "process.h"
#include <string.h>
#include <unistd.h>

#define NUM 101
#define STYLE '='

void process_v1() {
    char buffer[NUM];
    memset(buffer, 0, sizeof(buffer));  // 初始化缓冲区
    const char *lable = "|/-\\";  // 旋转光标字符
    int len = strlen(lable);
    int cnt = 0;

    while (cnt <= 100) {
        // 输出进度条:左对齐100字符、百分比、旋转光标,回车刷新
        printf("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt % len]);
        fflush(stdout);  // 手动刷新缓冲区
        buffer[cnt] = STYLE;  // 填充进度条
        cnt++;
        usleep(50000);  // 控制刷新速度
    }
    printf("\n");  // 进度完成后换行
}
(3)主程序main.c
#include "process.h"

int main() {
    process_v1();
    return 0;
}
(4)编写 Makefile

makefile

SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
BIN=processbar

$(BIN):$(OBJ)
	gcc -o $@ $^
%.o:%.c
	gcc -c $< -o $@
.PHONY:clean
clean:
	rm -f $(OBJ) $(BIN)

执行make生成processbar,运行后即可看到动态进度条效果。

3. 版本 2:适配业务场景的进度条

版本 1 为固定进度,版本 2 实现可适配下载、传输等业务场景的进度条,根据 “总任务量” 和 “已完成量” 动态更新进度:

(1)扩展头文件process.h
#pragma once
#include <stdio.h>
void process_v1();
void FlushProcess(double total, double current);
(2)扩展源文件process.c
#include "process.h"
#include <string.h>
#include <unistd.h>

#define NUM 101
#define STYLE '='

void process_v1() {
    // 版本1代码,此处省略
}

void FlushProcess(double total, double current) {
    char buffer[NUM];
    memset(buffer, 0, sizeof(buffer));
    const char *lable = "|/-\\";
    int len = strlen(lable);
    static int cnt = 0;  // 静态变量保存光标状态

    // 根据已完成量计算进度百分比
    int num = (int)(current * 100 / total);
    for (int i = 0; i < num; i++) {
        buffer[i] = STYLE;
    }
    double rate = current / total;
    cnt %= len;
    printf("[%-100s][%.1f%%][%c]\r", buffer, rate * 100, lable[cnt]);
    cnt++;
    fflush(stdout);
}
(3)编写业务主程序main.c
#include "process.h"
#include <unistd.h>

double total = 1024.0;  // 总任务量(示例为1024MB)
double speed = 1.0;     // 每次完成量

void DownLoad() {
    double current = 0;
    while (current <= total) {
        FlushProcess(total, current);
        usleep(3000);  // 模拟下载耗时
        current += speed;
    }
    printf("\ndownload %.2lfMB Done\n", total);
}

int main() {
    // 模拟多次下载
    for (int i = 0; i < 3; i++) {
        DownLoad();
    }
    return 0;
}

运行程序后,可看到模拟下载的动态进度条,每次下载完成后会提示下载成功。

四、总结

本文从 gcc/g++ 的编译流程出发,讲解了从源代码到可执行程序的完整转化过程;通过 Makefile 实现了项目的自动化构建;最后基于 Linux 终端特性,完成了基础版和业务适配版的进度条程序。这些知识点是 Linux C/C++ 开发的基础,掌握后可应对大部分小型项目的开发与构建需求。后续可进一步学习 gdb 调试、git 版本控制等技能,完善 Linux 开发技术栈。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐