You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用__attribute__((musttail))编译时尾调用损坏问题求助

clang搭配__attribute__((musttail))时线程化虚拟机调用栈损坏问题

问题分析

你遇到的问题根源有两点:

  • __attribute__((musttail))的严格约束:clang要求尾调用的参数必须是原函数参数的直接传递/提取值,无优化下按值传递包含指针的复杂结构体Block时,编译器无法正确复用栈帧,导致指针指向无效内存。
  • 栈容量初始化错误:cap被初始化为128 * sizeof(int64_t)(即1024字节),但sc是栈元素计数,当sc >= cap时,栈实际只能容纳128个int64_t元素,此时扩容逻辑会导致越界写入,破坏调用栈。

此外,移除Fn/Scope结构体后问题消失,是因为这简化了Block的内存布局,降低了编译器处理musttail时出错的概率;开启优化后编译器会自动修复栈布局和参数传递的问题,而fsanitize=memory会对内存访问做额外检查,避免了越界写入的影响。

解决方案

1. 修正栈容量逻辑

将栈容量的计数单位从字节改为元素数量,避免越界:

uint32_t cap = 128; // 初始容纳128个int64_t元素

void vm_init(void) { stack = malloc(cap * sizeof(int64_t)); }

void stack_push(int64_t v)
{
    if (++sc >= cap) {
        cap *= 2;
        stack = realloc(stack, cap * sizeof(int64_t));
    }
    stack[sc - 1] = v;
}

2. 改用指针传递Block结构体

避免按值传递复杂结构体,减少musttail处理时的栈帧冲突:

// 修改函数定义宏
#define F_INS(x) char x(int ic, Block *b)
// 修改尾调用宏
#define INSRET MUSTTAIL return b->ins[ic + 1].op(ic + 1, b);

// 对应修改各个指令函数
F_INS(Done) { return 0; }

F_INS(Imm)
{
    printf("IC: %u\n", ic);
    stack_push(b->ins[ic].imm);
    INSRET;
}

F_INS(Add)
{
    int64_t y = stack[--sc];
    int64_t x = stack[--sc];
    stack_push(x + y);
    INSRET;
}

// main函数中传递指针
(*b.ins[0].op)(0, &b);

3. 确保musttail的参数传递符合clang要求

clang的musttail要求尾调用的参数必须是原函数的输入参数,或仅对输入参数做简单提取(如指针访问),不能传递经过修改的局部结构体副本。改用指针传递后,参数直接复用原指针,完全符合musttail的约束。

复现原代码(修正转义后)

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

typedef enum { TypeInt = 0 } Tag;

typedef struct {
    int n; /* item count */
    int64_t *val; /* pointer */
} List;

typedef struct Block {
    struct Ins *ins;
    int n;
    struct Fn *fn;
    struct Scope *locals;
    struct Scope *mod;
} Block;

typedef struct Scope {
    int n;
    const char **names;
    int64_t *vals;
    Block first;
} Scope;

typedef struct Ins {
    char (*op)(int, Block);
    int64_t imm;
} Ins;

typedef struct Fn {
    Scope locals;
    Scope *mod;
} Fn;


int64_t *stack;
uint32_t sc; /* stack counter */
uint32_t fc; /* frame counter */
uint32_t cap = 128 * sizeof(int64_t);

#define F_INS(x) char x(int ic, Block b)
#define MUSTTAIL __attribute__((musttail))
#define INSRET MUSTTAIL return b.ins[ic + 1].op(ic + 1, b);

#define N_BUILTINS 1
const char *builtins[N_BUILTINS] = { "+" };

void vm_init(void) { stack = malloc(cap); }
F_INS(Done) { return 0; }

void stack_push(int64_t v)
{
    if (++sc >= cap) stack = realloc(stack, cap *= 2);
    stack[sc - 1] = v;
}

F_INS(Imm)
{
    printf("IC: %u\n", ic);
    stack_push(b.ins[ic].imm);
    INSRET;
}

F_INS(Add)
{
    int64_t y = stack[--sc];
    int64_t x = stack[--sc];
    stack_push(x + y);
    INSRET;
}


int main(int argc, char **argv)
{
    Block b = {0};
    vm_init();
    b.ins = realloc(b.ins, ++b.n * sizeof(Ins));
    b.ins[b.n - 1].op = &Imm;
    b.ins[b.n - 1].imm = 3;
    b.ins = realloc(b.ins, ++b.n * sizeof(Ins));
    b.ins[b.n - 1].op = &Imm;
    b.ins[b.n - 1].imm = 4;
    b.ins = realloc(b.ins, ++b.n * sizeof(Ins));
    b.ins[b.n - 1].op = &Add;
    b.ins = realloc(b.ins, ++b.n * sizeof(Ins));
    b.ins[b.n - 1].op = &Done;

    (*b.ins[0].op)(0, b);
}

环境信息

clang version 14.0.6
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

内容的提问来源于stack exchange,提问作者Jade

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 06:50:43