Linux基础开发工具详解:从yum到gdb的完整指南

前言

在Linux环境下进行C/C++开发,掌握基础开发工具是必不可少的技能。本文将系统介绍Linux下常用的开发工具,包括软件包管理器yum/apt、编辑器vim、编译器gcc/g++、自动化构建工具make/Makefile、版本控制器git以及调试器gdb。

一、软件包管理器

1.1 什么是软件包管理器

在Linux下安装软件,传统方式是下载源代码手动编译,但这样太繁琐。于是有人将常用软件提前编译好做成软件包,放在服务器上,通过包管理器可以方便地获取和安装。

  • yum:CentOS/RHEL系列使用的包管理器

  • apt:Ubuntu/Debian系列使用的包管理器

1.2 查看软件包

cpp 复制代码
# CentOS:查看lrzsz软件包
yum list | grep lrzsz

# Ubuntu:搜索软件包
apt search lrzsz

# Ubuntu:查看软件包详细信息
apt show lrzsz

1.3 安装软件

1.31替换 CentOS 7 源为国内镜像(关键)

CentOS 7 官方源已经停止维护,原来的 mirrorlist.centos.org 已经失效,需要替换为阿里云 / 清华镜像源:

  1. 备份原有源文件:

    复制代码
    mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
  2. 下载阿里云 CentOS 7 镜像源:

    复制代码
    curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
  3. 清除缓存并更新:

    复制代码
    yum clean all
    yum makecache
    yum update -y

完成上面三步后,重新执行安装命令:

cpp 复制代码
# CentOS
sudo yum install -y lrzsz

# Ubuntu
sudo apt install -y lrzsz

安装完成后,直接输入:

终端里就会出现一辆跑过的小火车 🚂

sl安装在哪里嘞

为什么是 /usr/bin/

Linux 里,用户可执行的系统命令,默认都放在 PATH 环境变量包含的目录里,比如:

  • /usr/bin/:绝大多数用户命令(lscdsl 都在这里)
  • /usr/local/bin/:你手动编译安装的程序,默认放这里

yum 安装的软件,都会自动把可执行文件放到 /usr/bin/,所以你直接敲 sl 就能运行,不用写完整路径 /usr/bin/sl

PATH 里存了一堆文件夹列表,系统规则:

当你只输入一个文件名(比如 sl、ls)系统会 自动依次去 PATH 里的所有文件夹里查找找到这个程序,就直接运行

1.4 卸载软件

cpp 复制代码
# CentOS
sudo yum remove -y lrzsz

# Ubuntu
sudo apt remove -y lrzsz

1.5 配置国内镜像源

国内常用的镜像源:

镜像站 链接
阿里云 https://developer.aliyun.com/mirror/
清华大学 https://mirrors.tuna.tsinghua.edu.cn/
中科大 http://mirrors.ustc.edu.cn/

二、编辑器Vim

2.1 三种基本模式

Vim有三种基本模式:

  • 命令模式(Normal mode) :控制光标移动、删除、复制等

  • 插入模式(Insert mode) :输入文字

  • 底行模式(Last line mode) :保存文件、退出、查找替换

2.2 常用操作

操作 命令
进入插入模式 i(光标前)/ a(光标后)/ o(新一行)
返回命令模式 ESC
进入底行模式 :
保存文件 :w
保存并退出 :wq
强制退出 :q!

2.3 光标移动

cpp 复制代码
h   # 左移
j   # 下移
k   # 上移
l   # 右移
gg  # 跳到文件开头
G   # 跳到文件末尾
w   #单词为单词向后移动
b   #单词为单位向前移动

2.4 删除与复制

cpp 复制代码
x       # 删除光标处字符
dd      # 删除整行
yy      # 复制整行
p       # 粘贴
u       # 撤销
Ctrl+r  # 恢复撤销
shift+x #删除关标右边的字符

多行操作 :前面加数字

3 yy 复制当前行和下面的两行

2.5 简单配置(~/.vimrc)

cpp 复制代码
syntax on          " 语法高亮
set nu             " 显示行号
set shiftwidth=4   " 缩进4空格

测试:

三、编译器gcc/g++

3.1 编译四个阶段

3.2 完整编译示例

创建hello.c

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

#define NUM 100

int main()
{
    printf("Hello, Linux!\n");
    printf("NUM = %d\n", NUM);
    return 0;
}

分步编译:

cpp 复制代码
# 1. 预处理(展开宏、头文件)
gcc -E hello.c -o hello.i

# 2. 编译(生成汇编代码)
gcc -S hello.i -o hello.s

# 3. 汇编(生成目标文件)
gcc -c hello.s -o hello.o

# 4. 链接(生成可执行文件)
gcc hello.o -o hello

# 一步到位编译
gcc hello.c -o hello

# 带调试信息编译(用于gdb调试)
gcc -g hello.c -o hello_debug

测试:

查看预处理的文件test.i。头文件展开了

3.3 静态库与动态库讲解

库就是一堆编译好的目标代码 (.o) 的集合,封装函数 / 接口,不暴露源码,只给别人调用

分为两种:

  • 静态库.a
  • 动态库.so

静态库(.a)

把多个 .a 目标文件 打包成一个归档文件。

编译链接原理:

编译时:把库中用到的代码直接拷贝进可执行程序

  • 运行时不再依赖原库文件
  • 程序体积大
  • 升级库需要重新编译整个程序

动态库(.so)

位置无关代码 PIC,打包成共享对象。

编译链接原理:

编译时只做符号引用 ,不拷贝代码;运行时才加载动态库到内存,多个程序共享同一份库。

  • 程序体积小
  • 升级库不用重新编译程序
  • 运行必须依赖 .so 文件

静态库 vs 动态库 核心对比:

特性 静态库 .a 动态库 .so
链接时机 编译时链接拷贝 运行时加载
程序体积
运行依赖 不依赖原库 必须有 .so
库升级 需重新编译程序 直接替换库即可
内存占用 每个程序独有副本 内存中只一份,多进程共享
扩展名 .a .so

3.4 静态链接与动态链接

:什么是链接

程序编译分两步:

  1. 编译.c.o 目标文件(只做语法、生成机器码,不处理外部符号)
  2. 链接 :把多个 .o + 库文件 合并、解析符号地址,生成可执行文件
静态链接:

链接阶段,把程序用到的库代码,直接复制合并到可执行文件内部 。对应 静态库 .a

核心特点:

  • 把库中相关机器码拷贝进最终程序
  • 生成的可执行文件体积大
  • 运行时不再依赖任何外部库文件,拷贝到别的机器也能直接跑
  • 库更新后,必须重新链接编译程序才能生效
  • 运行速度略快(不用运行时加载库)
动态链接

链接阶段不拷贝库代码 ,只记录「库名、函数符号」;等到程序运行时 ,再加载动态库 .so 到内存、绑定地址。

核心特点

  • 可执行文件体积小
  • 运行必须依赖对应的 .so 动态库,缺库直接报错
  • 多个程序可以共享同一份动态库内存,节省内存
  • 库升级直接替换 .so 即可,不用重新编译程序
  • 运行稍慢一点(多了运行时加载、符号绑定)
cpp 复制代码
静态链接方式:
gcc test.c -o test1
# 查看动态链接库依赖
ldd hello

# 静态链接方式(需要安装静态库)
gcc -static test.c -o test_static

动态链接方式:(ldd 查看动态链接库依赖)

得出:gcc编译器默认使用动态链接(g++也默认使用动态链接)

静态链接方式测试:

发现没有安装静态库,centos 7 yum 安装静态库:

检查静态库安装完成没有:(发现 .a 文件安装完成)

重新测试:

静态链接与动态链接可执行文件的大小对比:(分析静态链接体积比动态链接体积大的多)

静态链接查找不到动态链接库依赖

四、自动化构建make/Makefile

这是 Linux C 语言开发最核心的工具 ,用来自动编译、自动链接、自动清理,不用每次敲长长的 gcc 命令。

4.1什么是 make /Makefile?

  1. Makefile :一个文本文件 ,里面写好编译规则、依赖关系、命令
  2. make :一个命令工具 ,读取当前目录下的 Makefile,自动执行编译。

一句话作用一次写好规则,以后只输一个 make 命令,全自动编译整个项目。


Makefile 核心三要素(必考)

makefile

复制代码
目标文件 : 依赖文件
<Tab> 执行命令
  1. 目标 :要生成的文件(可执行文件、.o 文件)
  2. 依赖:生成目标需要哪些文件(.c、.h、库)
  3. 命令 :用什么命令生成目标(必须以 Tab 键开头,不能用空格!)

最简单标准 Makefile 模板(背会就能用)

makefile

复制代码
# 1. 目标:最终生成的可执行文件 myproc
# 2. 依赖:源文件 myproc.c
myproc: myproc.c
# 下面这行必须按 Tab 开头!
	gcc -o myproc myproc.c

# 清理命令(伪目标)
.PHONY: clean
clean:
	rm -f myproc

怎么使用?

  1. 编译(生成可执行文件)

    make

  • make 会自动找 Makefile
  • 检查依赖是否更新,只编译修改过的文件
  1. 运行程序

    ./myproc

  2. 清理编译文件

    make clean


两个重要知识点

1.伪目标 .PHONY

复制代码
.PHONY: clean
  • .PHONY: 目标名伪目标声明
  • 作用:让 make 知道这不是文件,是命令
  • 目的:防止目录里有同名文件,导致命令不执行
  1. 工作原理(重点)

  2. 找到目标文件

  3. 检查依赖文件是否更新(依赖的.c文件的Modify时间是否更新)

  4. 时间戳对比 :依赖比目标新 → 重新编译

  5. 执行命令生成目标

总结(必背)

  • Makefile = 编译规则文件
  • make = 执行编译的命令
  • 格式目标:依赖 + Tab 命令
  • 特点:自动检查更新,只编译修改的文件
  • clean :清理编译产物,配合 .PHONY 使用

测试:

make:生成可执行文件

运行(没有换行)

再次make:(发现失败,因为test依赖的源文件test.c没有更新)

4.2 多文件Makefile

假设我们有三个文件:

add.h

cpp 复制代码
#ifndef __ADD_H__
#define __ADD_H__

int add(int a, int b);

#endif

add.c

cpp 复制代码
#include "add.h"

int add(int a, int b)
{
    return a + b;
}

main.c

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

int main()
{
    int result = add(3, 5);
    printf("3 + 5 = %d\n", result);
    return 0;
}

Makefile

cpp 复制代码
# 编译器
CC = gcc

# 最终要生成的可执行文件
TARGET = main

# 所有源文件 .c
SRC = main.c add.c

# 所有目标文件 .o
OBJ = main.o add.o

# 第一步:链接生成可执行文件
$(TARGET): $(OBJ)
	$(CC) $(OBJ) -o $@

# 第二步:编译 main.c -> main.o
main.o: main.c add.h
	$(CC) -c main.c -o $@

# 第三步:编译 add.c -> add.o
add.o: add.c add.h
	$(CC) -c add.c -o $@

# 清理编译产物
.PHONY: clean
clean:
	rm -f $(TARGET) $(OBJ)

使用:

cpp 复制代码
make         # 编译
./main       # 运行
make clean   # 清理

五、第一个Linux程序:进度条

5.1 回车与换行

  • \r:回车(将光标移到行首)

  • \n:换行(将光标移到下一行)

  • Linux/Unix:\n同时完成回车+换行

  • Windows:\r\n才是回车+换行

5.2 行缓冲区演示

5.2.1 标准输入输出流(stdin、stdout、stderr)
cpp 复制代码
extern FILE *stdin;   // 键盘 读
extern FILE *stdout;  // 屏幕 普通输出
extern FILE *stderr;  // 屏幕 错误输出
  1. 先看懂每部分
  • FILE *:文件指针,专门用来操作读、写数据流
  • stdin标准输入 ,默认就是键盘
  • extern:意思是这个变量别人已经定义好了,我这里只是告诉编译器:有这么个东西,直接用就行,不用自己重新创建
  1. 一句话大白话

extern FILE *stdin; 就是声明一个代表键盘的文件指针,用来从键盘读数据。

  1. 小白最需要懂的

  2. 你不用自己写这行,#include <stdio.h> 里面已经帮你写好了

  3. scanf、fgets 底层默认都在用 stdin(键盘)

  4. 不用理解复杂原理,只要记住:stdin = 键盘输入

  5. 缓冲小白口诀

  • stdin、stdout:行缓冲 ,碰到 \n 才显示
  • stderr:无缓冲,立刻马上就显示
5.2.2什么是行缓冲区?

标准输出 stdout(printf 输出的位置)默认是 行缓冲模式

  • 缓冲区:一段内存,用来暂存输出内容
  • 行缓冲遇到换行符 \n 才会把内容刷到屏幕上
  • 没遇到 \n 时,数据一直存在缓冲区里,不显示
cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main()
{
    // 有\n:立即输出
    printf("带换行符\n");
    sleep(3);
    
    // 无\n:等待3秒后一次性输出
    printf("不带换行符");
    sleep(3);
    
    // 强制刷新缓冲区
    printf("强制刷新");
    fflush(stdout);
    sleep(3);
    
    return 0;
}
  • \n立刻输出
  • 不带 \n先存缓存
  • fflush(stdout)把当前缓冲区所有内容强制输出
  • 程序结束 → 自动刷新剩余内容

运行顺序(重点)

  • 立刻输出: 带换行符

  • 等待 3 秒

  • 等待 3 秒后,一次性输出: 不带换行符强制刷新

  • 再等待 3 秒

  • 程序结束

行缓冲的 3 种刷新时机(必背)

  1. 遇到换行符 \n
  2. 缓冲区满了(默认一般 1024 字节)
  3. 程序正常结束return 0 / exit()
  4. 手动调用 fflush(stdout)

5.3 倒计时程序

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

int main()
{
    int i = 10;
    while(i >= 0) {
        printf("%-2d\r", i);  // \r回车,覆盖输出
        fflush(stdout);        // 强制刷新
        i--;
        sleep(1);
    }
    printf("\n");
    return 0;
}

5.4 进度条完整代码

process.h

cpp 复制代码
#pragma once

#include <stdio.h>

void process_v1();
void FlushProcess(double total, double current);

process.c

cpp 复制代码
#include "process.h"
#include <string.h>
#include <unistd.h>

#define NUM 101
#define STYLE '='

// 版本1:简单进度条
void process_v1()
{
    char buffer[NUM];
    memset(buffer, 0, sizeof(buffer));
    const char* lable = "|/-\\";
    int len = strlen(lable);
    
    int cnt = 0;
    while(cnt <= 100) {
        printf("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt % len]);
        fflush(stdout);
        buffer[cnt] = STYLE;
        cnt++;
        usleep(50000);  // 50ms
    }
    printf("\n");
}

// 版本2:支持动态刷新
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);
}

main.c

cpp 复制代码
#include "process.h"
#include <unistd.h>

double total = 1024.0;
double speed = 1.0;

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

int main()
{
    // 简单进度条
    printf("=== 简单进度条 ===\n");
    process_v1();
    
    // 下载模拟
    printf("\n=== 文件下载模拟 ===\n");
    Download();
    
    return 0;
}

Makefile

cpp 复制代码
CC = gcc
CFLAGS = -Wall
TARGET = progress
OBJS = main.o process.o

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

%.o:%.c
	$(CC) $(CFLAGS) -c $< -o $@

.PHONY:clean
clean:
	rm -f $(OBJS) $(TARGET)
相关推荐
goyeer1 天前
【ITIL4】34服务实践 - 服务请求管理
运维·it·数字化·信息化·itil·信息化企业管理
运维全栈笔记1 天前
基于Docker的MinIO单机部署与功能测试指南
运维·docker·容器
杰 .1 天前
Linux工具使用
linux·服务器
Gc9umsbL11 天前
零基础学Linux:21天从“命令小白”到独立部署服务器
linux·运维·服务器
测试员周周1 天前
【AI测试功能5】AI功能测试的“黄金数据集“构建指南:从0到1搭建质量评估体系
运维·服务器·开发语言·人工智能·python·功能测试·集成测试
骑着骆驼写程序1 天前
Ubuntu上部署前端项目报500错误
linux·运维·ubuntu
心机之蛙qee1 天前
docker的安装(RHEL9)
运维·docker·容器
徐子元竟然被占了!!1 天前
TURN协议
运维
霍格沃兹测试学院-小舟畅学1 天前
Browserbase Skills:让 Claude Code 具备浏览器自动化能力的开源框架
运维·开源·自动化
小娄~~1 天前
进程间通信
linux·运维·服务器