You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

关于tcc将局部变量分配到栈上并生成memset指令的疑问

为什么TCC编译我的C代码会生成栈分配和memset指令?

首先得说,你的预期和TCC的输出差异,核心是C标准的语义要求加上TCC作为轻量编译器的极简编译策略导致的,咱们一步步拆解:

1. 你的C代码和手写汇编的语义根本不一样

你手写的汇编里,把timespec常量直接放在了代码段,用adr取地址传给syscall。但你的C代码里:

struct timespec const a = { .tv_sec = 10, .tv_nsec = 0 };

这个a函数内的自动变量——哪怕加了const,它本质还是栈帧上的局部变量,生命周期只在fun()调用期间。C标准要求自动变量的存储在栈上,而TCC是个“老实”的轻量编译器,它不会做“把栈上常量变量优化成只读段常量”这种高级优化,只会严格按C语义执行:先在栈上给a分配空间,再完成初始化。

2. 为什么会出现memset?

你以为初始化{10, 0}是直接把两个值写进栈空间?TCC的处理逻辑是:对结构体初始化,先把整个结构体的栈空间清零,再把显式初始化的成员写进去

看你objdump的结果,memset之后的指令就是在给tv_sectv_nsec赋值。这种“先清后写”的逻辑是TCC处理结构体初始化的通用方式——不管你有没有显式初始化所有成员,它都会先清空整个结构空间,再处理显式赋值,以此保证未初始化成员符合C标准的清零要求。

3. TCC的优化能力本来就很弱

GCC、Clang这类编译器在-O2以上优化级别,会把你这个const自动变量直接优化掉:要么把值直接塞寄存器传给nanosleep,要么把它提升到只读段。但TCC的定位是“快速编译的轻量编译器”,它几乎不做任何复杂优化,只会做最基础的编译步骤,所以不会帮你把栈上变量转换成代码段常量。

怎么让TCC生成类似你手写汇编的代码?

如果你想让a像手写汇编那样放在只读段,直接传地址,去掉栈分配和memset,只需要给a加个static

#include<time.h>

void fun() {
  asm("arg1:");
  static struct timespec const a = { .tv_sec = 10, .tv_nsec = 0 };
  asm("call_nanosleep:");
  nanosleep(&a,NULL);
  asm("return:");
}

static const变量会被放在只读数据段,生命周期覆盖整个程序运行期。TCC会直接用它的全局地址,不需要在栈上分配空间,也不需要memset初始化。你再用tcc -c编译后objdump,就能看到类似你预期的指令——直接取a的地址传给nanosleep,没有多余的栈操作和memset。

另外,如果你本来就想直接调用syscall而不是libc的nanosleep,那要么自己写汇编封装syscall,要么绕开libc的函数(TCC默认会链接libc,所以直接写nanosleep()肯定会调用libc的封装函数,也就是你看到的bl nanosleep而非svc指令)。

火山引擎 最新活动