📚 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
安装完成后逐个验证版本:
预期输出:
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
一条 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
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 # 清理
§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
用 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 客户端库):
用 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
💡 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 检查内存安全:
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 服务器和命令行工具