YAOTU INSIGHTS

c4droid安卓C++编译实战:工具链、多文件工程与Makefile

c4droid安卓C++编译实战:工具链、多文件工程与Makefile
简介C4Droid 手机 C 编程资源包面向希望在 Android 手机或平板上随时编写、编译和运行 C/C 代码的学习者与开发者适合课堂练习、算法验证、小型项目原型及无电脑环境下的移动编程。包内共 13 个文件以 7 个 apk 安装程序为主辅以 cpp、h 源码示例、txt 说明、png 截图及 zip 压缩包整体约 48.91MB便于在移动端快速部署编程环境。已有 724 人学习下载说明该工具在移动开发入门群体中具有一定参考价值。资源围绕 C4Droid 的终端式编辑、编译与运行流程展开兼顾 TCC 轻量编译和 GCC 插件扩展思路可配合 STL、SDL、Qt 等库了解图形界面、多媒体与网络通信程序的开发方式附带的终端、键盘与截屏类工具也能改善手机端编程体验适合作为移动 C 学习与调试的入门工具包。1. 从一部旧安卓机说起c4droid 把手机变成 C 编译终端手机编程工具最尴尬的地方不是功能少而是“看起来能写真跑起来处处断链”。某开发者把一台退役安卓机清空后装上 c4droid只保留编译器和键盘地铁上改两行 C、编译、看终端输出全程不碰电脑。c4droid 把 GCC/Clang 工具链、代码编辑器和终端塞进同一块屏幕能跑多文件工程也能把 SDL2 窗口推到手机上。它适合想用碎片时间验证算法、出门在外改小工程、或者给学生演示 C 编译链路的人不适合指望它替代桌面 IDE 的人。这一篇按“装好怎么验、工程怎么拆、图形库怎么链、翻车怎么查”的顺序拆一遍。2. 环境落地三条命令验工具链再跑通第一个多文件工程装好主程序和编译器插件之后别急着写业务代码。手机端最怕“编辑器能高亮编译器没装上”。C4droid 的编译按钮最终调用的还是 g 或 clang你看到的报错、链接顺序、头文件搜索路径跟桌面命令行同源。把终端当成黑匣子后面所有问题都只能猜。先让终端说话把编译器身份、头文件搜索路径、标准库可用性这三件事确认掉再谈工程结构。2.1 三条命令确认编译器不是摆设先在应用内终端进入工程目录执行下面三条命令。第一条看编译器是否存在第二条看预处理器的头文件搜索路径第三条看当前编译器默认的 C 标准宏。手机终端可能没有某些过滤工具没有就整段输出往下翻不要因为一条 sed 报错就以为编译器坏了。# 1. 看编译器身份与目标平台 g --version clang --version # 2. 看预处理器的头文件搜索路径 printf | g -E -v -x c - 21 # 3. 看默认标准宏和平台宏 printf | g -dM -E -x c - | grep -E __cplusplus|__ANDROID__第一条命令里的--version只做一件事确认 g 或 clang 能被当前 shell 找到。如果返回command not found说明编译器插件没装好或者 PATH 没指到插件目录后面写再多代码也没用。第二条命令里-E表示只做预处理-v打印详细过程-x c强制按 C 处理末尾的-表示从标准输入读空内容。重点看输出里#include ... search starts here:到End of search list.之间的路径这些就是尖括号头文件的搜索范围。第三条命令里的-dM -E会把预定义宏全部打印出来grep只留下__cplusplus和__ANDROID__。__cplusplus能看出默认标准是 C11、C14 还是 C17__ANDROID__能确认当前确实是安卓目标。常见误用是只看编辑器语法高亮就判断环境正常结果一编译就报std::filesystem不存在原因就是默认标准太低。后面所有编译命令都显式写-stdc17不要依赖默认值。提示如果clang不存在不用纠结以g的输出为准两个编译器混用时头文件和库路径不要交叉引用。2.2 最小工程先只依赖标准库验证工具链之后写一个只依赖标准库的最小样例。这个样例不要碰 SDL、不要碰第三方库只验证iostream、string、vector这三个最常见头文件能不能编译、链接、运行。手机键盘输入容易漏分号代码越短越好。// src/main.cpp #include iostream #include string #include vector int main() { std::vectorstd::string names{A, B, C}; for (const auto name : names) { std::cout hello, name \n; } return 0; }保存后执行下面两条命令。第一条创建输出目录第二条编译并运行。mkdir -p的-p表示目录已存在也不报错避免第二次执行时中断。mkdir -p build g -stdc17 -Wall -Wextra -O2 src/main.cpp -o build/hello ./build/hello参数逐个说清楚-stdc17固定语言标准避免不同编译器默认值不一样-Wall打开常见警告-Wextra打开额外警告手机端屏幕小警告比桌面更容易被忽略建议一开始就开-O2做常规优化调试阶段可以换成-O0 -g生成调试信息后报错位置更准但编译会慢一些-o build/hello指定输出文件不写的话默认输出到当前目录产物会和源码混在一起。最后的./build/hello是运行产物如果提示Permission denied先确认文件确实生成再执行chmod x build/hello。失败时只看第一条error不要被后面几十行吓到。fatal error: iostream: No such file or directory通常说明编译器插件不完整不是代码问题expected ;看对应行号undefined reference to std::cout说明链接阶段没找到标准库先回到 2.1 确认编译器身份。这个最小样例跑通才说明标准库链路正常。接下来再加文件、加头文件、加第三方库每加一层都单独验证一次出问题容易定位。注意调试阶段把-O2换成-O0 -g不要一边优化一边猜变量值手机端调试信息本来就不如桌面完整。2.3 用 build.sh 固定参数别每天手敲手机键盘输入成本高每次手敲一长串编译命令很容易漏参数。我一般会在工程根目录放一个build.sh把编译器、标准、警告、输出目录固定下来。脚本不复杂关键是可重复执行换一台手机或者换一个终端目录结果一致。#!/bin/sh set -eu CXX${CXX:-g} CXXFLAGS${CXXFLAGS:--stdc17 -Wall -Wextra -O2} ROOT$(CDPATH cd -- $(dirname -- $0) pwd) cd $ROOT mkdir -p build $CXX $CXXFLAGS -Isrc src/main.cpp -o build/hello ./build/helloset -eu让脚本遇到错误立即退出未定义变量也报错避免“编译失败了但脚本继续往下跑”的假成功。CXX${CXX:-g}表示外部没传CXX就用 g想切 clang 时执行CXXclang ./build.sh即可不用改脚本。CXXFLAGS同理调试时可以执行CXXFLAGS-stdc17 -Wall -Wextra -O0 -g ./build.sh。ROOT那行先取脚本所在目录再cd过去这样在别的目录执行脚本也不会找不到src/main.cpp。保存后执行chmod x build.sh再执行./build.sh。如果终端不支持CDPATH cd --这种写法简化成cd $(dirname $0)也能用别在兼容性上耗太久。常见失败是Permission denied原因是脚本没有执行权限not found看 shebang 第一行是不是/bin/shNo such file or directory先pwd和ls确认当前目录。脚本跑通之后后面加文件只需要改脚本里的源文件列表编译参数不用每天重新敲。3. 工程结构把单文件 demo 拆成 include/src/build 三层单文件能跑通只说明链路通真写东西很快会遇到重复定义、头文件找不到、链接顺序错。手机屏幕小更要把目录拆清楚。我一般用最小四层include放声明src放实现third_party放外部头文件和库build只放产物。不要把十几个.cpp堆在根目录后面加文件时连自己都找不到更别说排查链接错误。3.1 目录布局与 include 路径先建目录再放代码。目录名用英文小写短横线和下划线都比空格安全。安卓上路径大小写敏感桌面能跑不代表手机能跑统一小写能省很多事。mkdir -p include src third_party/include third_party/lib build assetsinclude放自己写的头文件src放实现和入口third_party/include放外部库头文件third_party/lib放外部库文件build只放.o和可执行文件assets放图片、字体、配置文件。这个布局不是强制标准但它能把“自己的代码”和“外部依赖”分开后面换库、删库、改链接路径时不容易误伤。接着写一个最小的多文件样例。头文件里只放声明实现文件里放定义入口文件只负责调用。#pragma once用来防止同一个头文件被重复包含比传统宏守卫少写几行。// include/math_utils.h #pragma once int add(int a, int b); int mul(int a, int b);// src/math_utils.cpp #include math_utils.h int add(int a, int b) { return a b; } int mul(int a, int b) { return a * b; }// src/main.cpp #include iostream #include math_utils.h int main() { std::cout add(2, 3) \n; std::cout mul(4, 5) \n; return 0; }编译命令要显式加-Iinclude让#include math_utils.h能找到头文件。引号包含的搜索顺序是先当前文件目录再-I指定的目录尖括号包含直接走系统路径和-I路径。自己的头文件用引号标准库和第三方库用尖括号这个习惯能减少路径混乱。g -stdc17 -Wall -Wextra -O2 -Iinclude \ src/main.cpp src/math_utils.cpp \ -o build/app如果报fatal error: math_utils.h: No such file or directory先pwd看当前目录再ls include看文件在不在最后检查-Iinclude有没有写错。如果报undefined reference to add(int, int)说明头文件找到了但实现文件没参与编译检查命令里有没有src/math_utils.cpp。这两个错误看起来都像“找不到”但一个发生在预处理阶段一个发生在链接阶段处理方式完全不同。3.2 Makefile把源文件列表和链接顺序固定下来源文件多了之后手写编译命令会越来越长。Makefile 的价值不是“高级”而是把源文件列表、头文件路径、链接库顺序固定下来改一个文件只重编一个文件。手机端不一定自带 make没有 make 就继续用build.sh有 make 就用下面的最小模板。CXX ? g CXXFLAGS ? -stdc17 -Wall -Wextra -O2 -Iinclude LDFLAGS ? LDLIBS ? SRC : $(wildcard src/*.cpp) OBJ : $(patsubst src/%.cpp,build/%.o,$(SRC)) TARGET : build/app all: $(TARGET) $(TARGET): $(OBJ) $(CXX) $(LDFLAGS) $^ -o $ $(LDLIBS) build/%.o: src/%.cpp mkdir -p $(dir $) $(CXX) $(CXXFLAGS) -MMD -MP -c $ -o $ -include $(OBJ:.o.d) clean: rm -rf build .PHONY: all cleanwildcard收集src下所有.cpppatsubst把src/xxx.cpp换成build/xxx.o这样新增源文件不用改 Makefile。$^表示所有依赖文件$表示目标文件$表示第一个依赖文件。-MMD -MP生成.d依赖文件-include把它们包含进来改头文件时相关源文件会自动重编。LDLIBS放在目标文件后面因为链接器从左到右解析库库放在源文件前面容易报未定义符号。执行make或make -j2。手机核心数少-j开太高反而发热降频-j2是比较稳的起点。常见错误Makefile: *** missing separator基本是缩进用了空格Makefile 的命令行必须以 tab 开头No rule to make target build/xxx.o看源文件路径和模式规则是否匹配。调试时执行make clean make做一次干净构建能排除旧产物干扰。3本文还有配套的精品资源点击获取