繁体   English   中英

为什么我的 C 代码调用 memmove(而不是 memcpy)

[英]Why does my C code call memmove (instead of memcpy)

我在 Linux 上使用 gcc 12.2。 我使用-nostdlib ,编译器抱怨缺少 memcpy 和 memmove。 所以我在汇编中实现了一个错误的 memcpy,并且我有 memmove 调用中止,因为我一直想使用 memcpy。

我想知道如果我在 C 中实现自己的函数,是否可以避免编译器要求 memcpy(和 memmove)。优化器似乎注意到它的真实含义并调用 C 函数。 然而,自从它被实施(我使用#define memcpy mymemcpy )并且因为我运行它,我看到我的应用程序中止。 它调用了我的 memmove 实现而不是程序集 memcpy。 为什么 gcc 调用移动而不是复制?

clang 调用 memcpy 但 gcc 更好地优化了我的代码,所以我用它来优化构建

__attribute__ ((access(write_only, 1))) __attribute__((nonnull(1, 2)))
inline void mymemcpy(void *__restrict__ dest, const void *__restrict__ src, int size)
{
    const unsigned char *s = (const unsigned char*)src;
    unsigned char *d = (unsigned char*)dest;
    while(size--) *d++ = *s++;
}

可重现

//dummy.cpp

extern "C" {
void*malloc() { return 0; }
int read() { return 0; }
int write() { return 0; }
int memcpy() { return 0; }
int memmove() { return 0; }
}

//main.cpp
#include <unistd.h>
#include <cstdlib>
struct MyVector {
    void*p;
    long long position, length;
};

__attribute__ ((access(write_only, 1))) __attribute__((nonnull(1, 2)))
void mymemcpy(void *__restrict__ dest, const void *__restrict__ src, int size)
{
    const unsigned char *s = (const unsigned char*)src;
    unsigned char *d = (unsigned char*)dest;
    while(size--) *d++ = *s++;
}

//__attribute__ ((noinline))
int func(const char*file_from_disk, MyVector*v)
{
    if (v->position + 5 <= v->length ) {
        mymemcpy(v->p, file_from_disk, 5);
    }
    return 0;
}

char buf[4096];
extern "C"
int _start() {
    MyVector v{malloc(1024),0,1024};
    v.position += read(0, v.p, 1024-5);
    int len = read(0, buf, 4096);
    func(buf, &v);
    write(1, v.p, v.position);
}

g++ -march=native -nostdlib -static -fno-exceptions -fno-rtti -O2 main.cpp 虚拟.cpp

使用objdump -D a.out | grep call检查objdump -D a.out | grep call

401040: e8 db 00 00 00          call   401120 <memmove>
40108d: e8 4e 00 00 00          call   4010e0 <malloc>
4010a3: e8 48 00 00 00          call   4010f0 <read>
4010ba: e8 31 00 00 00          call   4010f0 <read>
4010c5: e8 56 ff ff ff          call   401020 <_Z4funcPKcP8MyVector>
4010d5: e8 26 00 00 00          call   401100 <write>
402023: ff 11                   call   *(%rcx)

一个确切的答案需要深入研究 GCC 执行的代码转换并查看 GCC 如何转换您的代码。 这超出了我在合理时间内所能做的,但我可以用更一般的术语向您展示发生了什么,而无需深入研究 GCC 内部结构。

这是疯狂的部分:如果您删除inline ,您将获得memcpy 使用inline ,你得到memmove 我将在 Godbolt 上展示结果,然后讨论编译器如何工作来解释它。

代码

这是我放在Godbolt上的一些测试代码。

__attribute__ ((access(write_only, 1))) __attribute__((nonnull(1, 2)))
extern inline void mymemcpy(void *__restrict__ dest, const void *__restrict__ src, int size)
{
    const unsigned char *s = (const unsigned char*)src;
    unsigned char *d = (unsigned char*)dest;
    while(size--) *d++ = *s++;
}

void test(void *dest, const void *src, int size)
{
    mymemcpy(dest, src, size);
}

这是生成的程序集

mymemcpy:
        test    edx, edx
        je      .L1
        mov     edx, edx
        jmp     memcpy
.L1:
        ret
test:
        test    edx, edx
        je      .L4
        mov     edx, edx
        jmp     memmove
.L4:
        ret

是的,您可以看到一个函数正在转换为memcpymemmove 它不仅仅是相同的代码,它只是一个函数,它根据是否内联而进行不同的转换。 为什么?

优化过程如何工作

您可能会认为 C 编译器会执行如下操作:

  1. 预处理+标记化源文件,

  2. 解析创建 AST,

  3. 类型检查,

  4. 优化,

  5. 发出代码。

实际上,“优化”项是通过代码的许多不同遍历,并且这些遍中的每一个都以不同的方式修改代码。 这些遍在编译期间的不同时间发生,并且一些优化遍可能发生多次。

特定优化过程发生的顺序会影响结果。 如果您先执行优化 X,然后优化 Y,您会得到与先执行 Y,然后再执行 X 不同的结果。可能一个转换将信息从程序的一部分传播到另一部分,然后另一个不同的转换作用于该信息。

为什么这在这里相关?

你可以在这里看到有一个restrict指针srcdest 由于这些指针是restrict ,GCC“应该”能够知道memcpy是可以接受的,而memmove不是必需的。

但是,这意味着必须将srcdestrestrict指针的信息传播到最终转换为memmovememcpy的循环,并且必须在转换发生之前传播该信息。 您可以先轻松地将循环转换为memmove ,然后再确定参数是restrict ,但为时已晚!

看起来,不知何故,当函数被内联时, srcdestrestrict的信息正在丢失。 这为我们提供了几种不同的理论来解释为什么会发生这种情况:

  • 可能由于错误,内联后restrict的传播以某种方式被破坏。

  • 假设调用函数比被内联的函数具有更多的上下文,GCC 可能会在内联后从调用函数中推断出restrict

  • 也许优化传递没有以正确的顺序发生, restrict传播到循环。 也许该信息传播,然后内联执行,然后循环优化发生在这之后。

毕竟,优化通道(代码转换通道)对重新排序很敏感。 这是编译器设计的一个极其复杂的领域。

禁用优化

使用-fno-tree-loop-distribute-patterns ,或使用 pragma:

#pragma GCC optimize ("no-tree-loop-distribute-patterns")

简单使用-fno-builtin命令行选项。

https://godbolt.org/z/3Ys1s9jPr

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM