怎么做站旅游网站上泡到妞主流网站宽度
对比两种不同的链接配置方式,主要体现在是否使用标准库、启动文件和系统调用支持方式的不同。下面详细逐项对比:
🧩 一、两个配置
配置 1:使用标准启动方式 + nano libc
LIBS = -lc -lm -lnosys
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=... -Wl,--gc-sections
 
这是比较 标准、温和的嵌入式设置,特点如下:
| 项目 | 说明 | 
|---|---|
-lc -lm | 链接标准 libc (newlib) 和 libm (math) | 
-lnosys | 提供空的系统调用桩(用于防止链接 _kill, _getpid 等失败) | 
-specs=nano.specs | 使用 newlib-nano,是更轻量的 libc 版本 | 
没有 -nostdlib | 链接器仍然会使用 crt0.o 等默认启动代码 | 
✅ 适合需要 printf, malloc 的工程,并希望较小代码体积 | 
配置 2:完全裸机方式
LDFLAGS_GCC += \-nostdlib -nostartfiles \-specs=nosys.specs -specs=nano.specs
 
这是更为彻底控制的裸机配置:
| 项目 | 说明 | 
|---|---|
-nostdlib | 不链接任何标准运行时(包括 libc, libgcc, crt0) | 
-nostartfiles | 不链接 crt0.o, crti.o, crtn.o 等启动代码 | 
-specs=nosys.specs | 不启用系统调用(不自动拉入 _kill, _getpid) | 
-specs=nano.specs | 用 newlib-nano(但实际上 -nostdlib 会取消它除非手动链接) | 
❗ 必须自己提供:startup.s、_start、_exit、_write 等运行时支持 | |
| ✅ 适合极简/极控工程,如 bootloader、freestanding firmware | 
🆚 二、核心区别总结对比表:
| 特性 | 配置 1:标准启动 + newlib-nano | 配置 2:裸机全控方式 | 
|---|---|---|
启动文件(如 crt0.o) | ✅ 自动链接 | ❌ 不自动链接 | 
标准 C 库 (newlib) | ✅ 默认链接 (-lc) | ❌ 不链接(你要手动提供函数或库) | 
printf, malloc | ✅ 可用 | ❌ 除非你自己实现或手动链接 | 
链接器脚本(-T linker.ld) | ✅ 必须提供 | ✅ 必须提供 | 
系统调用符号(如 _sbrk) | ✅ 自动拉入 nosys.specs 或你自定义 | ❌ 你必须手动实现 | 
| 控制程度 | ⚪ 中等 | 🔴 极高,完全裸机 | 
🔧 三、用法建议:
✅ 使用标准库,但减小体积(推荐大多数项目)
LDFLAGS = -T linker.ld -specs=nano.specs -lc -lm -lnosys
 
适用于:USB、FreeRTOS、HAL、需要 printf、malloc 的项目。
你可以手动重定向 _write 来定制 printf 输出(如重定向到 UART、ITM)。
🔴 完全裸机 / 自己控制一切
LDFLAGS = -nostdlib -nostartfiles -T linker.ld -Wl,--gc-sections
 
适用于:bootloader、自定义运行时、不想用 libc 的极简项目。
你需要提供:
startup.s(或_start)_sbrk,_write,_exit,_kill这些裸函数- 不要使用 malloc、printf,或者自己实现
 
✅ 总结一句话:
- 你希望轻量而不想完全脱离标准库时 👉 用配置 1(
nano.specs,-lnosys,-lc -lm) - 你希望完全掌控链接行为并自己写所有底层实现时 👉 用配置 2(
-nostdlib -nostartfiles) 
当你自己写了 start.s(或 crt0.s)作为启动入口,并正确配置链接脚本使用你的 Reset_Handler 或 _start,就不会再链接系统自带的 crt0.o 或其他启动代码了。
🔍 为什么不会链接系统的 crt0.o?
 
默认情况下,GNU 工具链(如 arm-none-eabi-gcc)会自动链接标准 C 运行时启动代码 crt0.o(或 crtbegin.o、crtend.o):
- 但前提是你没有加 
-nostartfiles或-nostdlib - 或者你没有提供自定义的入口符号(比如 
Reset_Handler) 
一旦你这样做了,就不会链接系统的启动文件:
- 自己写了 
start.s/crt0.s,并提供了_start或Reset_Handler - 链接器参数中明确指定了 
ENTRY(Reset_Handler)或使用-nostartfiles 
这样,链接器认为你已经准备好了启动逻辑,不需要默认的启动文件,就不会链接它们了。
🚫 如果没注意,会隐式链接系统的 crt0
 
例如,如果你:
- 没有 
-nostartfiles - 没有自己的 
start.s - 用的是 
main(),但没指定入口函数 
那编译器会自动加上类似 /lib/gcc/.../crt0.o 来完成初始化。
这就是为啥你有时看到“程序居然能跑 main 但你没写启动文件”——是因为系统偷偷帮你加了。
✅ 总结一句话
只要你提供了自己的
start.s并控制链接过程(比如用-nostartfiles或指定入口),系统就不会再加crt0.o,你就是自己控制运行时的“导演”。
完全正确 ✅!
链接器(ld)识别你是否需要默认的启动文件(如 crt0.o),关键就是:
🔍 识别入口的两个方式:
✅ 方式 1:在链接脚本中写 ENTRY(...)
 
ENTRY(Reset_Handler)
 
- 明确告诉链接器程序从哪开始执行
 - 通常你写了自己的 
start.s,就会加这个 - 会跳过默认的 
crt0.o 
✅ 方式 2:链接器默认查找 _start
 
如果你没写 ENTRY(...),链接器默认使用 _start 作为入口:
.global _start
_start:...
 
- 如果你提供了 
_start,链接器会用它,不再链接系统的_start(如 crt0.o) - 否则它会去找系统默认的启动文件来提供 
_start 
🔧 补充控制选项
| GCC 选项 | 意义 | 
|---|---|
-nostartfiles | 不自动加 crt0.o 等启动文件(但还保留标准库) | 
-nostdlib | 更彻底,连 libc(如 newlib/libm) 都不加 | 
-Wl,-e,XXX | 显式告诉链接器使用 XXX 作为程序入口,和 ENTRY() 是一样的意思 | 
-T script.ld | 指定你自己的链接脚本,通常其中会有 ENTRY() | 
✅ 总结一句话:
链接器只要看到你指定了
ENTRY()或你自己提供了_start(或Reset_Handler),它就不会再偷偷地加系统的crt0.o。
如果你想彻底掌控裸机程序启动过程,那就要做到:
- ✅ 写你自己的 
start.s - ✅ 提供 
ENTRY(...) - ✅ 明确使用 
-nostartfiles(可选) - ✅ 提供自己的链接脚本 
.ld 
说到 crt0(C runtime zero),它是程序启动时的“第一块敲门砖”,非常关键。
crt0 到底干了啥?
简单来说,crt0 是程序启动时执行的第一段代码,负责为你的程序搭建运行环境,让 main() 能正常运行。
crt0 的主要工作:
-  
设置堆栈指针(Stack Pointer)
在裸机环境下,程序一开始,堆栈指针(SP)没有被设置,crt0 会帮你把 SP 指向正确位置。 -  
初始化数据段(.data)
把存储在 Flash 中的初始化变量复制到 RAM。 -  
清零 BSS 段(.bss)
清理未初始化的全局变量区(把它们清成 0)。 -  
调用全局构造函数
对 C++ 来说,调用__libc_init_array(),执行所有全局和静态对象的构造函数。 -  
调用 main() 函数
运行完初始化后,跳转到用户写的main(),开始程序主体。 -  
处理程序结束
在裸机一般不会返回,但在某些环境,crt0 会处理 main 返回后的清理和退出。 
形象比喻
- crt0 就是舞台灯光和幕布的拉开动作,帮演员(main 函数)做好上场准备。
 
总结
| crt0 负责 | 
|---|
| 初始化硬件环境(堆栈指针) | 
| 复制初始化数据(.data) | 
| 清空未初始化数据区(.bss) | 
| 调用全局/静态构造函数(C++) | 
| 进入主函数 main() | 
当然可以!以下是一个适用于 ARM Cortex-M(如 STM32)的最小 crt0.s 启动文件示例,内容包括堆栈初始化、.data/.bss 初始化和进入 main() 的全过程。
🔧 示例:最小裸机 crt0.s
 
    .syntax unified.cpu cortex-m3.thumb.global _start.global Reset_Handler.global main.extern __libc_init_array/* 堆栈顶地址,由链接脚本定义 */.section .isr_vector, "a", %progbits.word _estack          /* 初始堆栈指针 */.word Reset_Handler    /* 复位中断向量 */.text.thumb_func
Reset_Handler:
_start:/* 初始化 .data 段 */ldr r0, =_sidata       /* Flash 中 .data 的起始地址 */ldr r1, =_sdata        /* RAM 中 .data 的目标起始地址 */ldr r2, =_edata        /* RAM 中 .data 的结束地址 */
data_copy:cmp r1, r2ittt ltldrlt r3, [r0], #4strlt r3, [r1], #4blt data_copy/* 清零 .bss 段 */ldr r1, =_sbssldr r2, =_ebssmovs r3, #0
bss_zero:cmp r1, r2strlt r3, [r1], #4blt bss_zero/* 如果是 C++,调用构造函数数组 */bl __libc_init_array/* 跳转到 main */bl main/* 如果 main 返回,就停在这里 */
hang:b hang
 
🧩 链接脚本对应段(摘录)
你的链接脚本(.ld 文件)要定义这些符号:
_estack = ORIGIN(RAM) + LENGTH(RAM); /* 栈顶 */_sidata = LOADADDR(.data); /* .data 在 Flash 中的起始 */
_sdata = ADDR(.data);      /* .data 在 RAM 中的起始 */
_edata = .;                /* .data 在 RAM 中的结束 */_sbss = .;                 /* .bss 起始 */
_ebss = .;                 /* .bss 结束 */
 
✅ 支持 C/C++ 都可以
- 如果你用 C++,
__libc_init_array会自动处理全局/静态对象构造。 - 如果你只用 C,也可以不调用 
__libc_init_array。 
适用范围
✅ 适用于:
- STM32F1/F4/F7 等 Cortex-M 系列
 - 裸机工程(无 RTOS,无 HAL)
 
