GCC使用
326.2.4GCC使用
#include <stdio.h> // 引入标准输入输出头文件,为 printf 提供函数声明。
int main() // 定义程序入口函数,返回值类型为 int。
{ // main 函数体开始。
printf("i am wfeng!\n"); // 向标准输出打印字符串,并用 \n 换行。
while (1); // 条件恒真;末尾分号构成空循环体,程序会一直忙等。
return 0; // 表示正常结束;因前面的无限循环持续运行,本行实际不可达。
} // main 函数体结束。
标题下源码是视频最后一次编译时的完整状态。第一次编译时还没有 while (1);,其余代码相同;教师随后加入这一行,专门演示前台任务、进程状态和信号。这里仅整理缩进、空格并逐行补充注释,没有替换课程源码或加入库实现。
视频环境为 Ubuntu 20.04,普通用户是 wfeng,主要在 ~/test 目录操作。本节的核心不是用编辑器“运行 C 文件”,而是用 GCC 把 C 源文件构建成主机上的 ELF 程序,再观察文件权限、进程状态和信号控制。
一、课程介绍
GCC 在本节中是编译器驱动程序,不是编辑器。执行 gcc test.c 时,它会按需要协调预处理、编译、汇编和链接,最后生成可运行程序;源文件仍由 Vim 等文本编辑器编写。
原笔记把 GCC 多次识别为“JCC”,并称“从 51 单片机到 32 位系统的所有代码都用同一个 GCC 编译”,这两点都不准确:
- 正确名称是 GCC,命令是小写的
gcc。 - 视频中的
gcc是 Ubuntu 主机工具链,画面生成的是 x86-64 Linux 程序。 - 面向 51、ARM 等其他处理器或裸机环境时,需要与目标指令集、ABI、运行库和链接脚本匹配的编译器或交叉工具链。C 语言具有可移植性,不代表同一个可执行文件能跨处理器运行,也不代表所有平台使用完全相同的 GCC 命令。
二、安装GCC
视频先输入 gcc,系统提示命令不存在,并建议安装 gcc 软件包;随后实际执行:
# 按视频方式,用 APT 安装 Ubuntu 软件源中的 gcc 软件包。
sudo apt install gcc
画面中的软件包版本为 4:9.3.0-1ubuntu2,下载量约 5,208 B、新增占用约 51.2 kB。这只说明当时系统已具备大部分依赖,APT 此次主要补装元软件包,不能据此认为完整编译器只有几 KB。版本、下载量和依赖会随系统状态及软件源变化。
安装完成后可核验:
# 查看当前 PATH 实际找到的 gcc。
command -v gcc
# 查看 GCC 版本;无需使用 sudo。
gcc --version
若要进行常见的 Ubuntu C/C++ 本机构建,通常还会安装包含 GCC、G++、make、libc 开发文件等基础组件的 build-essential;但本视频明确安装并使用的是 gcc,不能把扩展建议写成画面实录。普通用户编译自己有权读写的文件也不需要 sudo。
三、文件操作
视频进入 ~/test 后,目录里原有 a.c、c.txt 和误拼为 teat.tar 的归档文件;本节新建 test.c。下面按真实操作顺序说明。
3.1 创建文件并编写代码
视频执行:
# 进入当前用户家目录下已有的 test 目录。
cd ~/test
# 用 Vim 打开或新建 test.c。
vim test.c
在 Vim 中按 i 进入插入模式,第一次写入标题下代码但没有 while (1);。写完后按 Esc 回到普通模式,再输入 :wq 并回车,保存并退出。注意是连续输入 :wq,不是原转录中带空格的 : wq。
源码的关键点如下:
#include <stdio.h>为printf提供声明;它不是“导入整个库实现”。- 标准形式是
int main()或更明确的int main(void);课程源码采用前者。 printf("i am wfeng!\n");打印一行文本,\n是换行转义序列。- 初始版本执行
return 0;后正常结束。最终版本在它之前加入无限空循环,所以无法自然运行到return 0;。
3.2 查看文件
视频保存后用下面的命令核对文本:
# 把 test.c 的文本内容输出到终端。
cat test.c
随后编译并查看文件类型:
# 让 file 根据文件内容特征描述生成物。
file a.out
# 查看源文件的类型描述。
file test.c
画面中 file a.out 的主要结果是:
ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, ... not stripped
file test.c 显示为 C source, ASCII text。Ubuntu 20.04 的 GCC 默认会生成位置无关可执行文件(PIE),当时的 file 版本可能把它描述为 shared object;结合执行权限和 ELF 头,它仍是可启动的程序。反过来,不能把“ELF”直接等同于“可执行文件”,因为 ELF 格式也用于目标文件、共享库和 core 文件。
file 会依次使用文件系统、魔数和语言等测试推断类型,其输出是识别结果,不是对文件安全性或程序正确性的保证。
视频还执行 ls 和 ls -l:
# 列出目录项;是否着色取决于 ls 别名、选项和终端配色。
ls
# 显示类型、权限、链接数、属主、属组、大小和时间等信息。
ls -l
画面中可执行文件显示为绿色,是当时 ls --color 的配色效果。判断权限应看 ls -l 的权限位,不能依赖颜色;颜色既可关闭,也可由用户自定义。
3.3 编译文件
第一次实际编译命令是:
# 编译并链接 test.c;成功时默认生成当前目录下的 a.out。
gcc test.c
命令成功时通常不输出文字,不能把“没有输出”理解为没有执行。可查看退出状态或只在成功后继续:
# 只有编译成功时才运行刚生成的程序。
gcc test.c && ./a.out
第一次运行的输出为:
i am wfeng!
这里使用 ./a.out,是因为 Shell 通常不会在 PATH 中搜索当前目录;./ 明确给出了当前目录中的路径。课程演示用最简命令即可,日常学习更建议开启基本诊断:
# 扩展写法:启用常用警告,按 C11 编译,并指定清晰的输出名。
gcc -Wall -Wextra -std=c11 test.c -o test
这些选项是规范化补充,不是视频第一次编译时输入的内容。警告也不等于错误:构建是否继续取决于诊断级别和选项,但警告应当阅读并处理。
3.3.1 编译并命名文件
GCC 的 -o 文件名 用于指定最终输出路径。视频画面实际输入的是:
# 画面原样:把输出文件误拼成 ptintf,但命令本身仍然有效。
gcc test.c -o ptintf
# 运行该误拼名称的可执行文件。
./ptintf
因此生成物叫 ptintf,不是原笔记所写的 printf。文件名拼错不会自动报错,只要该路径合法,GCC 就按给定名称创建输出。为了表达用途,建议写成:
# 推荐命名:生成名为 test 的程序。
gcc test.c -o test
# 运行推荐命名的程序。
./test
-o 前后位置只要符合 GCC 的选项解析都可以,但 gcc 源文件 -o 输出文件 和 gcc -o 输出文件 源文件 应保持名称对应清楚。不要把输出文件取名为现有源文件,否则有覆盖重要文件的风险。
3.4 文件权限
ls -l 权限字段如 -rwxrwxr-x:第一个字符表示文件类型,普通文件为 -;后九位依次分成所有者、所属组、其他用户三组,每组都是 rwx。
| 权限 | 数值 | 对普通文件的含义 |
|---|---|---|
r | 4 | 可读取内容 |
w | 2 | 可修改内容 |
x | 1 | 允许把它作为程序或脚本执行,但文件仍须具有有效格式或解释器 |
每一组把所需数值相加,所以:
| 数字模式 | 符号形式 | 含义 |
|---|---|---|
754 | rwxr-xr-- | 所有者可读写执行,组可读执行,其他用户只读 |
444 | r--r--r-- | 三类用户都只有读权限 |
777 | rwxrwxrwx | 所有人都能读、写、执行,通常权限过宽 |
视频依次进行了几组实验:
sudo chmod 777 a.c后,a.c在当时的颜色配置中变绿。- 编译生成的
a.out和ptintf起初都有执行位,显示为-rwxrwxr-x。 sudo chmod 754 a.out后,./a.out仍可运行。sudo chmod 444 a.out移除执行位后,./a.out报Permission denied,而未改权限的./ptintf仍可运行。./a.c并没有运行成 C 程序,而是出现大量command not found:仅增加x不会编译源码。内核无法把该随机文本识别为本机可执行格式后,Bash 又尝试把它当 Shell 输入解释,才产生这些报错。- 最后执行
sudo chmod 777 a.out,恢复了执行能力,但这不是值得照搬的最小权限方案。
视频中这些文件属于用户 wfeng,所以修改自己的文件不需要 sudo。更合适的做法是只添加确实需要的权限,例如:
# 只给文件所有者添加执行权限,不开放给所有人写入。
chmod u+x a.out
# 或明确设置成所有者 rwx、同组和其他用户 r-x。
chmod 755 a.out
“有 x”只是执行的必要条件之一,不代表文件内容有效或安全;脚本还需要正确的解释器声明,二进制文件要与当前内核和体系结构兼容。实际访问还可能受 ACL、只读文件系统、noexec 挂载、安全模块等机制影响。
3.5 ifconfig指令
视频直接执行 ifconfig,画面中 ens33 的 IPv4 地址为 192.168.31.230、掩码为 255.255.255.0、广播地址为 192.168.31.255,回环接口 lo 为 127.0.0.1。这些值只是录制当时虚拟机的网络快照,不能作为其他机器的固定配置。
ifconfig 来自较老的 net-tools 工具集,可查看和配置网络接口,但现代 Linux 通常更推荐 iproute2:
# 简洁显示各接口地址。
ip -br address
# 查看路由;默认网关通常在这里,而不是 ifconfig 输出中。
ip route
本节只查看信息,没有修改接口配置。不要在远程连接中随意停用或改写正在使用的网卡,否则会中断连接。
3.6 echo指令
视频从网页复制示例时,把网页中的提示符也一并粘贴了:
[root@localhost ~]# echo "hello world"
Shell 因而尝试执行名为 [root@localhost 的内容,并报告 command not found。方括号、用户名、主机名、目录和 # 是教程展示的提示符,不属于命令。正确输入是:
# 输出一个字符串并换行。
echo "hello world"
输出为 hello world。echo 的选项和反斜杠处理在不同 Shell 中可能有差异;脚本若要求格式稳定,可用:
# 用明确的格式字符串输出一行文本。
printf '%s\n' 'hello world'
这里的 printf 是 Shell 命令或内建命令,与前面误拼的可执行文件名 ptintf、C 源码中的 printf 函数要区分。
3.7 ping指令
视频只口头回顾了上一节“从虚拟机 ping Windows 主机”的连通性测试,本节没有再次执行并展示具体 ping 命令。
ping 通过 ICMP 回显请求观察目标是否响应以及往返时间。能 ping 通只证明当时 ICMP 路径可达,不代表 SSH、HTTP 等具体服务一定开放;ping 不通也可能只是防火墙禁用了 ICMP,不能单独断言整台主机离线。
3.8 任务管理器
Windows 的“任务管理器”只是帮助初学者建立类比。Linux 终端中,视频演示了 ps 和 top:
# 显示一次进程快照;无选项时默认选择范围较窄。
ps
# 动态刷新系统摘要和进程/线程列表。
top
画面中的无参数 ps 只列出当前终端里的 bash 和本次 ps。准确地说,默认选择通常限制为与调用者具有相同有效用户 ID 且关联同一终端的进程,不是“当前窗口中的全部进程”。
top 会周期刷新,显示负载、任务状态、CPU、内存以及当前视图中的进程或线程。它的字段、排序、筛选和显示数量都可以配置,所以也不应绝对化为“必然显示系统所有进程”。
3.9 ps和top命令
视频还输入了 ps -aux,确实得到了很长的进程列表。但在 procps-ng 的 ps 中,Unix 风格选项和 BSD 风格选项可以混用,ps -aux 存在历史歧义,手册明确不推荐把它当成规范形式。常用写法是:
# BSD 风格:查看较广范围的进程及常用字段。
ps aux
# Unix 风格:查看所有进程的完整格式。
ps -ef
ps 是某一时刻的快照;top 则反复采样并刷新。退出 top 的首选按键是 q,视频实际也用过 Ctrl+C:它通常向前台 top 发送 SIGINT 并让其结束。
Ctrl+Z 绝不是同义的“退出”。它通常向前台进程组发送 SIGTSTP,使 top 停止并返回 Shell,但进程仍然存在。可用以下作业控制命令处理:
# 查看当前 Shell 管理的后台或停止作业。
jobs -l
# 把最近的停止作业恢复到前台。
fg
如果只是想离开 top,使用 q 最清楚;误按 Ctrl+Z 后应恢复或终止该作业,不要遗留大量停止进程。
3.10 进程与线程
进程和线程不能简单等同于“软件”和“软件里的小功能”:
- 进程提供运行程序所需的资源与隔离上下文,例如虚拟地址空间、打开文件和安全凭据。
- 线程是进程内的执行流。同一进程的线程共享地址空间和许多进程资源,但各自具有栈、寄存器状态和调度属性。
- 一个新进程最初至少有一个线程,之后可以创建更多线程。现代浏览器等应用往往同时使用多个进程,每个进程又可包含多个线程,所以“一个应用就是一个大进程”只能作为非常粗略的入门类比。
课程后续面向 MCU 上的 RT-Thread,常以“多线程/多任务”模型理解并发:多个任务通常共享同一地址空间。这适用于课程所针对的典型资源受限场景,但不能据此推导所有 RTOS、所有 RT-Thread 配置或所有 Linux 程序都不存在进程隔离。
3.11 应用案例
本节案例通过一个无限空循环制造持续占用 CPU 的前台任务,再分别用终端控制键、ps、top 和 kill 观察及控制它。实验目的应理解为学习进程状态和信号,而不是把任何“没有返回提示符”的程序都认定为崩溃。
3.11.1 例题: 进程杀死演示
视频重新打开 test.c,在 printf 后加入标题下源码中的:
while (1); // 条件恒为真,分号是空循环体;该线程持续忙等,无法到达下一条语句。
然后再次执行 gcc test.c,覆盖原来的 a.out。运行 ./a.out 后,程序先打印一次 i am wfeng!,随后一直执行空循环。此时没有 Shell 提示符,是因为前台程序仍在运行、Shell 正在等待它结束;它不是“卡死后已经退出”。空循环还会持续消耗一个逻辑 CPU 的可用时间,不应作为真实程序的等待方案。
Ctrl+C 与 Ctrl+Z 的区别:
| 操作 | 终端通常发送的信号 | 默认效果 | 视频现象 |
|---|---|---|---|
Ctrl+C | SIGINT | 中断并终止前台进程 | 出现 ^C,程序结束并返回提示符 |
Ctrl+Z | SIGTSTP | 停止前台进程,不销毁它 | 出现 [1]+ Stopped ./a.out,作业仍在 |
程序可安装处理器改变某些信号的效果,所以“按键必然杀死任何进程”也不严谨。SIGKILL 和 SIGSTOP 是两个不能被捕获、阻塞或忽略的例外。
视频先用 Ctrl+Z 留下 PID 9477 的停止进程,状态为 T;又启动一个 PID 9478 的实例,使其处于运行状态并接近占用一个 CPU 核心。PID 只属于录制当时,实际操作必须重新查找:
# 仅查看命令名为 a.out 的进程,并显示 PID、状态和命令。
ps -C a.out -o pid,stat,cmd
# 查看当前 Shell 的作业及 PID,尤其适合发现 Ctrl+Z 留下的作业。
jobs -l
视频在第二个终端中执行 kill -9 9478,运行实例随即显示 Killed。-9 表示发送 SIGKILL,目标无法清理临时文件、刷新缓冲区或正常释放应用层资源,因此不应作为第一选择。规范顺序是:
# 默认发送 SIGTERM,请求目标正常终止;PID 必须换成现场确认的值。
kill PID
# 只有目标确认无响应、且 PID 再次核对无误时,才强制终止。
kill -KILL PID
应先给程序处理 SIGTERM 的时间,再检查是否退出。PID 会变化并可能被系统复用,复制视频中的 9478 可能误伤无关进程。视频只强制结束了运行中的 9478,先前被 Ctrl+Z 停止的 9477 仍可能存在;实验结束必须用 jobs -l 或 ps 复查并清理,不能看到前台窗口出现 Killed 就认定所有实例都消失。
系统信息:
视频随后执行:
# 只输出内核名称;画面结果为 Linux。
uname
# 汇总输出内核名、主机名、内核发行版、版本、机器架构等字段。
uname -a
画面中的内核发行版为 5.15.0-105-generic,机器架构为 x86_64。它们只是录制系统的值;uname -a 也不是“所有系统信息”,不会完整给出发行版包版本、磁盘、网络等内容。按需查询更清晰:
# 查看内核发行版。
uname -r
# 查看机器硬件名称/体系结构。
uname -m
关机、取消关机和重启:
视频实际先执行不带参数的 shutdown。在 Ubuntu 20.04 的 systemd 实现中,省略时间会按 +1 处理,即约一分钟后关机;画面明确提示可用 shutdown -c 取消。原转录识别出的 shutdown -5 并不是画面命令。
# 立即请求关机并断电;会终止会话和服务,执行前先保存工作。
sudo shutdown now
# 明确指定立即 power-off;在该 systemd 版本中 -h 等价于 --poweroff。
sudo shutdown -h now
# 计划 10 分钟后关机。
sudo shutdown +10
# 在计划尚未进入立即关机阶段时取消。
sudo shutdown -c
# 立即重启。
sudo shutdown -r now
reboot 也用于重启,但它与 shutdown 是两个命令,不能写成 shutdown reboot。这些操作会影响整台机器;在虚拟机、服务器或 SSH 环境中要先确认目标主机、保存数据并通知其他用户,不能为了验证笔记随意执行。
四、本节关键纠正
| 原笔记、转录或容易误解的说法 | 精校后的结论 |
|---|---|
| “JCC”是编辑器 | 正确名称是 GCC;本节的 gcc 是编译器驱动程序,不负责编辑文本 |
| 从 51 到 32 位系统都使用同一个 GCC | 目标架构和运行环境不同,需要匹配的本地或交叉工具链;源码可移植不等于二进制通用 |
| GCC 安装只下载约 5 KB,所以编译器很小 | 该数字只是录制系统补装软件包时的增量,已有依赖未重复下载 |
file 显示 ELF 就等于可执行文件 | ELF 还可承载目标文件、共享库等;需结合 ELF 类型、解释器、架构和权限判断 |
| 文件变绿就一定能运行 | 颜色取决于 ls 配置;执行还要求权限、有效格式/解释器及兼容环境 |
chmod 777 是恢复执行权限的通用方法 | 777 允许所有人写入,通常过宽;应按最小权限使用 u+x、755 等 |
给 a.c 增加执行位就能执行 C 源码 | 执行位不会编译内容;视频中的随机文本被 Shell 尝试解释后产生报错 |
自定义输出文件名是 printf | 视频实际误输为 ptintf;规范示例可另取 test 等清晰名称 |
网页中的 [root@localhost ~]# 也要复制 | 那是提示符;真正命令只有 echo "hello world" |
无参数 ps 显示当前窗口全部进程 | 默认通常只选同一有效用户且同一终端的进程,是一次快照 |
ps -aux 是唯一规范写法 | 视频确实如此输入,但存在语法歧义;推荐 ps aux 或 ps -ef |
Ctrl+C 与 Ctrl+Z 都是退出/杀死 | 前者通常发送 SIGINT,后者发送 SIGTSTP 并保留停止作业 |
while (1); 让进程“卡死” | 它执行有分号的无限空循环,进程仍在运行并持续消耗 CPU |
杀进程默认使用 kill -9 | 先用默认 SIGTERM 请求正常结束,确认无响应后才考虑 SIGKILL |
视频结束 9478 后所有实例都消失 | 之前由 Ctrl+Z 停止的 9477 可能仍在,必须复查并清理 |
uname -a 显示系统全部信息 | 它只汇总若干内核和机器字段,值也随主机变化 |
shutdown -5 是视频命令 | 画面实际是无参数 shutdown,默认约一分钟后执行,并可用 shutdown -c 取消 |
核对依据
- 本地
videos/326.2.4GCC使用.mp4:逐段核对 GCC 安装、test.c两个版本、默认和自定义输出名、file/权限实验、ifconfig、echo、ps/top、两个a.out进程、kill -9、uname与关机提示;视频时长约 17 分 43 秒。 - 本地
transcripts/326.2.4GCC使用.txt与.srt:用于核对教师讲述,并结合画面纠正JCC、printf/ptintf、chmod、while、kill、shutdown等语音识别错误。 - GCC 官方手册:Overall Options与Invoking GCC:核对预处理、编译、汇编、链接阶段,默认
a.out和-o输出选项。 - Ubuntu Focal
apt手册:核对软件包安装命令的语义和交互边界。 - GNU Coreutils:权限设置与
chmod调用方式:核对符号/数字权限及chmod行为。 file(1)上游手册页:核对文件系统、魔数和语言测试以及类型描述的边界。ps(1)上游手册页与top(1)上游手册页:核对ps默认选择、快照性质、ps -aux歧义、top动态显示及退出/暂停行为。- Bash 作业控制、Bash 作业控制内建命令与
signal(7)上游手册页:核对Ctrl+C、Ctrl+Z、jobs、fg、SIGTERM、SIGKILL和不可捕获信号。 pthreads(7)上游手册页:核对同一进程内线程共享资源及线程各自属性。- Ubuntu Focal
shutdown手册:核对省略时间等价于+1、now、-h、-r和-c。