必须用-mcmodel=medlow当裸机环境链接静态可执行文件且所有代码和数据落在0x80000000附近2 gib内,它要求符号地址可用auipc+addi访问,不依赖got,适用于bootloader、启动代码等freestanding场景。

什么时候必须用 -mcmodel=medlow
当你在裸机环境(比如 MCU 或 QEMU 裸跑)中链接成单个静态可执行文件,且所有代码和全局数据都落在 0x80000000 这类固定起始地址附近(典型如 2 GiB 地址窗口内),-mcmodel=medlow 是安全默认。它要求所有符号地址能用一条 auipc + addi 指令对访问(即 PC-relative ±2 GiB),不依赖 GOT。
常见错误现象:undefined reference to `__global_pointer$' 或链接时提示重定位类型不支持(如 R_RISCV_PCREL_HI20 超出范围),往往就是误用了 medany 却没配好全局指针或 GOT。
- 裸机启动代码、bootloader、freestanding 环境基本都该用
medlow - 若链接脚本把
.text和.data都约束在0x80000000–0xffffffff内,medlow足够且更紧凑 - 不支持动态链接;如果后续要加 dlopen 或共享库,这条路就走不通
什么场景下得切到 -mcmodel=medany
-mcmodel=medany 放宽了符号地址限制:只要每个符号自身落在某个 2 GiB 区域内即可,不要求全部挤在同一个 ±2 GiB 窗口里。它会为非本地符号生成 GOT 条目,并用 lw + auipc 访问——代价是多一次内存加载、多一个 GOT 表项。
典型使用场景:
- 你正在构建 Linux 用户态程序,且目标系统内存布局分散(比如内核模块、用户空间驱动)
- 工程包含多个独立编译单元,各自链接基址不同(例如插件式架构)
- 你用
-fpic或准备支持PIE,但又不想升级到large模型(后者需三条指令寻址)
注意:medany 不等于“支持动态链接”——它只是让重定位更灵活;是否能动态链接还取决于 -mabi(如 lp64d vs ilp32)和链接器选项(-shared)。
-mcmodel 和 -march/-mabi 的耦合关系
-mcmodel 不是孤立参数,它和 ABI、地址宽度强相关:
-
riscv64下-mcmodel=medlow默认配合-mabi=lp64;若你用-mabi=lp64f或lp64d,模型本身不变,但浮点寄存器约定变了 -
riscv32下medlow对应 ±2 GiB,而medany仍只保证单个 2 GiB 区域——但因 XLEN=32,实际可用虚拟地址空间更小,容易撞边界 - 混用
-mcmodel=medany和-march=rv32e可能触发警告:E 扩展删减了通用寄存器,GOT 访问更易受寄存器压力影响
验证方式:编译后用 riscv64-unknown-elf-objdump -d 看函数内是否有 lw t0,XX(gp) 类 GOT 访问——有则说明 medany 生效;若全是 auipc+addi,大概率是 medlow。
容易被忽略的调试线索
真正卡住的往往不是选错模型,而是模型和链接脚本、启动代码没对齐:
- 链接脚本里写了
. = 0x100000000;(超出 32 位地址),却用medlow—— 编译器会静默生成非法auipc,运行时报非法指令 - 裸机程序启用了
medany,但启动汇编没初始化gp寄存器(即没设 global pointer),结果所有 GOT 访问都读错地址 - 用
clang编译时忘了传--target=riscv64-unknown-elf,LLVM 可能回退到 host 默认模型,导致-mcmodel被忽略
最稳妥的做法:裸机起步一律 -mcmodel=medlow,等明确需要跨区域符号引用时,再同步改链接脚本、初始化 gp、加 GOT 段,并用 objdump 对照验证。模型切换不是开关,是整条工具链的契约。











