我该如何阻止GCC将这个按字节复制的操作优化成memcpy调用?

9

我有一段代码用于实现标准C库中的memcpy,它一次复制一个字节将内存从src复制到dest:

void *memcpy(void *restrict dest, const void *restrict src, size_t len)
{
    char *dp = (char *restrict)dest;
    const char *sp = (const char *restrict)src;

    while( len-- )
    {
        *dp++ = *sp++;
    }

    return dest;
}

gcc -O2编译生成的代码表现合理:

memcpy:
.LFB0:
        movq    %rdi, %rax
        testq   %rdx, %rdx
        je      .L2
        xorl    %ecx, %ecx
.L3:
        movzbl  (%rsi,%rcx), %r8d
        movb    %r8b, (%rax,%rcx)
        addq    $1, %rcx
        cmpq    %rdx, %rcx
        jne     .L3
.L2:
        ret
.LFE0:

然而,在使用 gcc -O3 的情况下,GCC会将这个简单的逐字节复制操作优化为一个 memcpy 调用:

memcpy:
.LFB0:
        testq   %rdx, %rdx
        je      .L7
        subq    $8, %rsp
        call    memcpy
        addq    $8, %rsp
        ret
.L7:
        movq    %rdi, %rax
        ret
.LFE0:

这种做法行不通(memcpy 会无条件地调用自身),并且会导致段错误。

我尝试了传递 -fno-builtin-memcpy-fno-loop-optimizations,但仍然出现相同的问题。

我使用的是 GCC 版本 8.3.0:

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-cros-linux-gnu/8.3.0/lto-wrapper
Target: x86_64-cros-linux-gnu
Configured with: ../configure --prefix=/usr/local --libdir=/usr/local/lib64 --build=x86_64-cros-linux-gnu --host=x86_64-cros-linux-gnu --target=x86_64-cros-linux-gnu --enable-checking=release --disable-multilib --enable-threads=posix --disable-bootstrap --disable-werror --disable-libmpx --enable-static --enable-shared --program-suffix=-8.3.0 --with-arch-64=x86-64
Thread model: posix
gcc version 8.3.0 (GCC) 

我该如何禁用优化,使得复制操作不被转换成 memcpy 函数调用?

2个回答

14

这里有一件似乎足够的事情:在编译 memcpy 单元时,使用 -fno-builtin 而非 -fno-builtin-memcpy

另一种选择是传递 -fno-tree-loop-distribute-patterns。虽然这可能很脆弱,因为它禁止编译器首先重组循环代码,然后将部分代码替换为对 mem* 函数的调用。

或者,由于不能依赖 C 库中的任何内容,也许可以考虑使用 -ffreestanding


5

这样做是不行的(memcpy 无条件地调用自身),并且会导致段错误。

重新定义 memcpy 是未定义行为

如何禁用优化,以便将复制转换为 memcpy 调用(最好在仍然使用 -O3 编译的情况下)?

不要这样做。最好的方法是修复您的代码:

  • 在大多数情况下,您应该使用另一个名称。

  • 在罕见的情况下,如果您真的正在实现 C 库(如评论中所讨论的那样),并且您确实想要重新实现 memcpy,那么您应该使用编译器特定的选项来实现。对于 GCC,请参见 -fno-builtin*-ffreestanding, 以及 -nodefaultlibs-nostdlib


2
如果这个答案至少尝试解决问题,那就太好了,因为目前它没有提供任何有用的信息(除了告诉我修复代码或重命名函数)。是否可能提供一个解决方案(而不是“不要这样做”),以帮助我摆脱未定义行为的困境? - S.S. Anne
2
@JL2210 如果我现在理解你的问题,你呈现给我们的函数不应该被称为“memcpy”,因为你感兴趣的是防止普通的循环复制被编译器重写为memcpy调用。但由于你命名为“memcpy”,这表明你的秘密意图也是要用自己的代码替换memcpy,这带来了新的问题,Acorn已经开始解决了。 - Johan Boulé
1
如果您提供一个基于汇编语言的memcpy实现而不是基于C语言的实现,那么您是否可以摆脱编译器最终从memcpy本身调用memcpy的问题? - Johan Boulé
2
@JL2210 对于那些没有自己优化代码(即汇编代码)的架构,保留原始的内置memcpy可能是有意义的。此外,我认为除非你明确表示,否则编译器不会调用你自己的版本。而且你如何处理ODR,它是否适用于编译器内置函数? - Johan Boulé
2
@AndrewHenle 嗯,如果 OP 真的在实现 memcpy(),他们应该告诉编译器禁用整个标准 C 库... - cmaster - reinstate monica
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接