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)
相关推荐
A小辣椒1 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒5 小时前
TShark:基础知识
linux
AlfredZhao7 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式