📚 Termux 无 Root 学习开发系列
当前进度:阶段二 · 7/28
阶段一:环境搭建与终端基础 ✅ 已完成
阶段二:编程开发环境
06 Python 开发环境搭建
07 C/C++ 开发环境搭建(当前篇)
08 Node.js 开发环境
09 Git 版本控制
阶段三至九共 19 篇,后续持续更新

07 C/C++ 开发环境:编译、调试与构建工具链

难度:入门→进阶 | 前置:06 Python 开发环境搭建
在手机上用 clang 21 编译 C/C++ 程序,用 gdb 调试,用 make/cmake 管理项目
读完本篇你将能:在 Termux 上安装 Clang 21 编译器并验证版本,编写编译运行第一个 C/C++ 程序,理解预处理→编译→汇编→链接四步流程,掌握常用编译选项(警告、优化、调试、宏定义),用 Make 和 CMake 管理多文件项目,用 GDB 定位段错误,用 AddressSanitizer 检测内存越界,最终完成一个温度转换器完整项目。
📑 本文目录
01安装编译工具链
02第一个 C 程序
03第一个 C++ 程序
04编译选项详解
05Make 构建工具
06CMake 构建工具
07GDB 调试
08内存安全检查
09头文件与库
10实操项目:温度转换器

上一篇你搭建了 Python 开发环境,体验了解释型语言的即写即跑。本篇进入编译型语言的世界——C/C++。与 Python 不同,C/C++ 代码需要经过"编译"才能运行,这个过程涉及预处理、编译、汇编、链接四个阶段。Termux 自带 LLVM 工具链,无需安装 Android NDK,直接用 clang 就能在手机上编译原生 aarch64 程序。

§1 安装编译工具链

一条命令安装全套 C/C++ 开发工具:

Shell
pkg install clang make cmake gdb binutils pkg-config

安装完成后逐个验证版本:

Shell
clang --version

预期输出:

Output
clang version 21.0.0
Target: aarch64-unknown-linux-android24
Thread model: posix

验证 make 和 gdb:

Shell
make --version
gdb --version
Output
GNU Make 4.4
GNU gdb (GDB) 15.1
💡 为什么用 clang 不用 gcc?
Termux 早已弃用 gcc 包,clang 是唯一推荐的 C/C++ 编译器。Clang 是 LLVM 项目的前端编译器,相比 gcc 有三大优势:编译速度快(前端解析更高效)、错误提示友好(精确指出问题位置和修复建议)、内存占用低(适合手机环境)。你之前安装的 Python、Node.js 底层都是 clang 编译的。如果需要全套 LLVM 工具(含 llvm-as、llvm-link 等),可额外 pkg install llvm。

§2 第一个 C 程序

用 Neovim 创建 hello.c,写入经典 Hello World:

C (hello.c)
#include <stdio.h>
 
int main(void) {
    printf("Hello, Termux!\n");
    return 0;
}

编译并运行:

Shell
clang hello.c -o hello
./hello
Output
Hello, Termux!

一条 clang hello.c -o hello 看似简单,实际经历了四个阶段:

阶段 操作 产物
1. 预处理 展开 #include、#define、条件编译 hello.i(纯 C 代码)
2. 编译 C 代码→汇编代码 hello.s(汇编)
3. 汇编 汇编代码→机器码 hello.o(目标文件)
4. 链接 合并目标文件 + 库 hello(可执行文件)

用 -E 只做预处理,看看 #include <stdio.h> 展开后是什么(输出很长,这里只看尾部):

Shell
clang -E hello.c | tail -10
Output
int main() {
  printf("Hello, Termux!\n");
  return 0;
}

stdio.h 的内容已被展开插入到文件前面,你自己的 main 函数出现在最后。用 -S 可以生成汇编代码:

Shell
clang -S hello.c
cat hello.s

你会看到 aarch64 汇编指令——这就是你的 C 代码被翻译成的机器语言的人类可读形式。

§3 第一个 C++ 程序

C++ 用 clang++ 编译。创建 hello.cpp:

C++ (hello.cpp)
1
2
3
4
5
6
7
8
9
10
#include <iostream>
 
int main() {
    std::cout << "Hello, C++ Termux!"
            << std::endl;
    return 0;
}
 
// 编译: clang++ hello.cpp -o hello
// 运行: ./hello
Shell
clang++ hello.cpp -o hello
./hello
Output
Hello, C++ Termux!

C++ 标准选择(通过 -std 标志):

标准 标志 状态
C++17 -std=c++17 默认推荐
C++20 -std=c++20 完整支持
C++23 -std=c++23 实验性,部分支持

§4 编译选项详解

clang 提供丰富的编译选项,掌握这些是写出可靠 C 代码的基础:

选项 作用 示例
-Wall -Wextra 开启常见警告 clang -Wall -Wextra hello.c
-O2 / -O3 优化级别 clang -O2 hello.c
-g 生成调试信息 clang -g hello.c
-o 指定输出文件名 clang hello.c -o myapp
-I 头文件搜索路径 clang -I./include hello.c
-L 库文件搜索路径 clang -L./lib hello.c
-l 链接指定库 clang hello.c -lm
-D 定义预处理宏 clang -DDEBUG hello.c

优化级别对比:

级别 说明 适用场景
无 -O 不优化,代码与源码对应 调试(配合 -g)
-O1 基础优化,减少体积 日常开发
-O2 标准优化,速度与安全平衡 发布版本
-O3 激进优化,可能增大体积 性能关键路径
💡 实用建议
开发阶段固定用 -Wall -Wextra -g -O0(开全部警告 + 调试信息 + 不优化),发布时用 -O2。永远不要忽略警告——C 语言的一个警告可能就是一个潜在的段错误。

§5 Make 构建工具

当项目只有一个 .c 文件时,直接 clang 编译就够了。但当项目有多个源文件时,手动敲编译命令既繁琐又容易出错——这就是 Make 的用武之地。Make 通过 Makefile 文件定义编译规则,自动管理依赖关系。

先看一个最简单的 Makefile:

Makefile
1
2
3
4
5
6
7
8
CC = clang
CFLAGS = -Wall -Wextra -g
 
hello: hello.c
    $(CC) $(CFLAGS) hello.c -o hello
 
clean:
    rm -f hello
💡 Makefile 三要素
目标(hello/clean):要生成的东西
依赖(hello.c):生成目标需要的输入
命令(clang ...):执行的动作
注意:命令行必须用 Tab 键缩进,不能用空格!Neovim 中按 Ctrl-V Tab 可输入真正的 Tab。

Makefile 变量和自动变量:

变量 含义
CC = clang 自定义变量,用 $(CC) 引用
$@ 当前目标名
$< 第一个依赖文件
$^ 所有依赖文件(去重)

多文件项目实战

创建一个三文件项目:main.c 调用 utils.c 中的 add 函数。

main.c:

C (main.c)
1
2
3
4
5
6
7
8
#include <stdio.h>
#include "utils.h"
 
int main(void) {
    int result = add(3, 4);
    printf("3 + 4 = %d\n", result);
    return 0;
}

utils.h:

C (utils.h)
1
2
3
4
5
#ifndef UTILS_H
#define UTILS_H
 
int add(int a, int b);
#endif

utils.c:

C (utils.c)
1
2
3
4
5
#include "utils.h"
 
int add(int a, int b) {
    return a + b;
}

Makefile(多文件版):

Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CC = clang
CFLAGS = -Wall -Wextra -g
TARGET = app
SRCS = main.c utils.c
 
$(TARGET): $(SRCS)
    $(CC) $(CFLAGS) -o $@ $^
 
# 单独编译某个 .o(增量编译)
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@
 
clean:
    rm -f $(TARGET) *.o
Shell
make         # 编译项目
./app         # 运行
make clean     # 清理
Output
3 + 4 = 7

§6 CMake 构建工具

Make 的痛点是跨平台兼容性差——Windows 上 Makefile 不好用,而且大型项目的 Makefile 手写非常繁琐。CMake 解决了这个问题:你写一份 CMakeLists.txt,CMake 自动生成对应平台的 Makefile(或 Ninja、VS 工程等)。

对比项 Make CMake
输入文件 Makefile CMakeLists.txt
跨平台 弱(依赖 Unix 工具) 强(生成对应平台工程)
依赖管理 手动写 find_package 自动查找

用 CMake 管理上面的三文件项目,创建 CMakeLists.txt:

CMake (CMakeLists.txt)
1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.10)
project(MyApp C)
 
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_FLAGS "-Wall -Wextra -g")
 
add_executable(app main.c utils.c)

CMake 标准流程——out-of-source 构建(编译产物不污染源码目录):

Shell
1
2
3
4
5
mkdir build && cd build
cmake ..         # 生成 Makefile
make           # 编译
./app          # 运行
cd .. && rm -rf build # 清理

§7 GDB 调试

C 语言没有 Python 那样的异常 traceback,程序崩溃时只输出一个 "Segmentation fault" 就退出了——你完全不知道错在哪里。GDB 是 GNU 调试器,能在程序崩溃时暂停并告诉你崩溃位置。

先写一个有 bug 的程序——缓冲区溢出导致段错误:

C (bug.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
 
void copy_data(char *dest) {
    strcpy(dest, "This is too long!");
}
 
int main(void) {
    char buf[10];
    copy_data(buf);
    printf("%s\n", buf);
    return 0;
}

编译时务必加 -g 生成调试信息,且不加优化(-O0):

Shell
clang -g -O0 bug.c -o bug
./bug
Output
Segmentation fault

用 GDB 启动程序,崩溃时会自动暂停并显示位置:

GDB Session
1
2
3
4
5
6
7
8
9
10
11
12
gdb ./bug
(gdb) run
Starting program: ./bug
Program received signal SIGSEGV, Segmentation fault.
 
(gdb) bt           # 查看调用栈
#0  copy_data (dest=...) at bug.c:5
#1  main () at bug.c:10
 
(gdb) list 5       # 查看第5行附近代码
(gdb) quit         # 退出
 

GDB 常用命令速查:

命令(简写) 作用
break / b 设置断点(如 b main)
run / r 运行程序
next / n 单步执行(不进入函数)
step / s 单步执行(进入函数)
continue / c 继续运行到下一断点
print / p 打印变量值
backtrace / bt 查看调用栈
list / l 查看源代码
quit / q 退出 GDB
💡 GDB 兼容性注意
部分新设备(如 Galaxy S25 等搭载最新高通骁龙芯片的机型)上 gdb 可能因 ptrace 限制而无法正常工作。替代方案:在代码中加 printf 定位问题,或直接用下一节介绍的 AddressSanitizer——它在编译时注入检查代码,不依赖 ptrace,兼容性更好。

§8 内存安全检查

在桌面 Linux 上,valgrind 是最常用的内存检查工具。但在 Termux 上,由于 Android 内核限制,valgrind 不可用。替代方案是 Clang 内置的 AddressSanitizer(ASan)——编译时加一个标志,运行时自动检测内存错误。

写一个有越界访问的程序:

C (overflow.c)
1
2
3
4
5
6
7
8
#include <stdio.h>
 
int main(void) {
    int arr[5] = {1, 2, 3, 4, 5};
    arr[10] = 999; // 越界写入!
    printf("%d\n", arr[10]);
    return 0;
}

用 AddressSanitizer 编译并运行:

Shell
clang -fsanitize=address -g overflow.c -o overflow_asan
./overflow_asan
Output
1
2
3
4
5
6
=================================================================
==ERROR: AddressSanitizer: stack-buffer-overflow
WRITE of size 4 at 0x... by 'main':
    #0 0x... in main overflow.c:5:5
    #1 0x... in ... libc.so
SUMMARY: AddressSanitizer: stack-buffer-overflow overflow.c:5

ASan 精确报告了错误类型(stack-buffer-overflow)、操作类型(WRITE)、位置(overflow.c:5)。valgrind 与 ASan 对比:

对比项 valgrind AddressSanitizer
Termux 可用 不可用 可用
工作方式 运行时模拟 CPU 指令 编译时注入检查代码
性能开销 大(20-50 倍慢) 中等(2 倍慢)
无需重编译 是 否(需加 -fsanitize)

§9 头文件与库

理解 Termux 的头文件和库文件位置,是用好第三方 C 库的前提。

类型 路径 说明
头文件 /data/data/com.termux/files/usr/include 所有 .h 文件
库文件 /data/data/com.termux/files/usr/lib 所有 .a / .so 文件

安装第三方 C 库,例如 libcurl(HTTP 客户端库):

Shell
pkg install libcurl

用 pkg-config 自动获取编译和链接参数:

Shell
pkg-config --cflags --libs libcurl
Output
-I/data/data/com.termux/files/usr/include -lcurl

数学库是 C 标准库的一部分,但需要显式链接 -lm:

C (math_demo.c)
1
2
3
4
5
6
7
8
#include <stdio.h>
#include <math.h>
 
int main(void) {
    double x = sqrt(2.0);
    printf("sqrt(2) = %f\n", x);
    return 0;
}
Shell
clang math_demo.c -o math_demo -lm
./math_demo
Output
sqrt(2) = 1.414214
💡 bionic 与 glibc 的差异
Termux 使用 Android 的 bionic libc 而非桌面 Linux 的 glibc。bionic 更轻量但不支持部分 glibc 扩展函数(如 fts_open、getpwnam_r 的某些行为不同)。大部分标准 C 程序无需修改即可编译运行。如果编译开源项目遇到 glibc 专属函数报错,通常需要打 patch 或找替代实现。autoconf/automake 工具链也可用:pkg install autoconf automake libtool。

§10 实操项目:温度转换器

综合运用本篇学到的编译、Makefile、GDB 和 AddressSanitizer,完成一个温度转换器。项目结构:

项目结构
~/projects/temp-converter/
├── temp_converter.c    # 主程序
└── Makefile             # 构建脚本

temp_converter.c(完整代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
">26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdlib.h>
 
double c_to_f(double c) {
    return c * 9.0 / 5.0 + 32.0;
}
 
double f_to_c(double f) {
    return (f - 32.0) * 5.0 / 9.0;
}
 
void print_usage(void) {
    printf("用法: ./temp_converter <温度> <单位>\n");
    printf("单位: C (摄氏) 或 F (华氏)\n");
    printf("示例: ./temp_converter 25 C\n");
}
 
int main(int argc, char *argv[]) {
    if (argc != 3) {
        print_usage();
        return 1;
    }
 
    double temp = atof(argv[1]);
    char unit = argv[2][0];
 
    if (unit == 'C' || unit == 'c') {
        printf("%.1f C = %.1f F\n", temp, c_to_f(temp));
    } else if (unit == 'F' || unit == 'f') {
        printf("%.1f F = %.1f C\n", temp, f_to_c(temp));
    } else {
        printf("错误: 未知单位 '%c'\n", unit);
        print_usage();
        return 1;
    }
 
    return 0;
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#585b70;">2
3
4
5
6
7
8
9
10
11
12
13
14
CC = clang
CFLAGS = -Wall -Wextra -g
TARGET = temp_converter
 
$(TARGET): temp_converter.c
    $(CC) $(CFLAGS) temp_converter.c -o $(TARGET) -lm
 
debug: $(TARGET)
    gdb ./$(TARGET)
 
asan:
    $(CC) -fsanitize=address -g temp_converter.c -o $(TARGET)_asan -lm
    ./$(TARGET)_asan
 
clean:
    rm -f $(TARGET) $(TARGET)_asan

编译运行:

Shell
make
./temp_converter 25 C
./temp_converter 100 F
Output
25.0 C = 77.0 F
100.0 F = 37.8 C

用 AddressSanitizer 检查内存安全:

Shell
make asan
Output
25.0 C = 77.0 F
 
# ASan 无错误报告,程序内存安全
💡 tmux + Neovim 联动
用 tmux 分屏:左窗格编辑代码,右窗格运行 make 和 ./temp_converter。修改代码后 :w 保存,右窗格 ↑ make 重新编译——这就是 C 语言开发的日常循环。
✏️ 动手练习
🟢 基础验证
编写一个 factorial.c,用 for 循环计算 5 的阶乘并打印结果。用 clang -Wall -g factorial.c -o factorial 编译,确认无警告,运行输出 120。
参考思路:定义 int result = 1;,循环 for (int i = 1; i <= 5; i++) result *= i;,最后 printf 打印。
🟡 组合应用
将温度转换器拆分为多文件项目:converter.c(转换函数)+ converter.h(头文件)+ main.c(主程序)。编写 Makefile 管理编译,用 make 一键构建。
提示:参考 §5 的 main.c + utils.c + utils.h 结构。Makefile 中用 SRCS = main.c converter.c 管理多个源文件。
🔴 开放挑战
用 CMake 重写温度转换器的构建系统,添加 CMAKE_BUILD_TYPE 选项支持 Debug 和 Release 两种模式:Debug 用 -g -O0,Release 用 -O2。用 cmake -DCMAKE_BUILD_TYPE=Release .. 切换模式。
方向:在 CMakeLists.txt 中用 if(CMAKE_BUILD_TYPE STREQUAL "Debug") 分支设置不同 CFLAGS。out-of-source 构建时 build 目录互不干扰,可以同时维护 debug-build 和 release-build 两个目录。
📖 知识回顾
Clang 21 安装 C/C++ 编译流程 编译选项 Make 构建工具 CMake 跨平台 GDB 调试 AddressSanitizer 头文件与库路径 pkg-config bionic libc
下篇预告
08 Node.js 开发环境
将搭建 Termux 上的 Node.js 开发环境:安装 Node.js LTS、npm 包管理、编写 Express 服务器和命令行工具
关注公众号持续获取系列更新