NO BUILTIN!

(尝试在遍地 AI 的时代古法炼丹)


# 前言

做毕设的实验阶段,在移植 CoreMark 时,遇到了测试 elf 文件运行爆栈的错误。

经过定位,发现是 memset 函数生成的汇编代码死循环了。

# 问题描述

问题 C 代码如下:

void* memset(void* s, int c, size_t n) {
    unsigned char* p = (unsigned char*)s;
    while (n--) {
        *p++ = (unsigned char)c;
    }
    return s;
}

经过默认参数,开启 -O2 的 gcc 编译后,生成了如下代码:

80001d84 <memset>:
80001d84:	ff010113          	addi	sp,sp,-16
80001d88:	00812423          	sw	s0,8(sp)
80001d8c:	00112623          	sw	ra,12(sp)
80001d90:	00050413          	mv	s0,a0
80001d94:	00060663          	beqz	a2,80001da0 <memset+0x1c>
80001d98:	0ff5f593          	zext.b	a1,a1
80001d9c:	fe9ff0ef          	jal	80001d84 <memset>    # ❌ 递归调用自身
80001da0:	00c12083          	lw	ra,12(sp)
80001da4:	00040513          	mv	a0,s0
80001da8:	00812403          	lw	s0,8(sp)
80001dac:	01010113          	addi	sp,sp,16
80001db0:	00008067          	ret

# 原因分析

为什么 C 函数中没有调用自身的指令,生成的汇编代码却会出现那一条递归指令呢?

问题出在 gcc 的优化中。以下是相关的编译流程:

  1. gcc 识别到循环为 memset 的语义;
  2. 由于没有 -fno-builtin 参数,gcc 认为存在 memset 内置函数,尝试将函数体替换为内置函数;
  3. 本函数名恰好为 memset ,进行函数解析时即找到了当前函数的符号。

# 解决方法

最简洁的修复方式:

__attribute__((optimize("no-builtin")))
void* memset(void* s, int c, size_t n) {
    unsigned char* p = (unsigned char*)s;
    while (n--) {
        *p++ = (unsigned char)c;
    }
    return s;
}

或者如果 GCC 版本较老(不支持 no_builtin):

__attribute__((optimize("no-tree-loop-distribute-patterns")))
void* memset(void* s, int c, size_t n) {
    unsigned char* p = (unsigned char*)s;
    while (n--) {
        *p++ = (unsigned char)c;
    }
    return s;
}

# 相关链接