为什么MSVC会优化掉这个memcpy调用?

15

我有以下的C代码(我删去了其他一些调用和检查):

#include <stdint.h>
#include <memory.h>

extern char buffer[];

extern void getstr1(char *buff, int buflen);
extern void getstr2(char **s);
extern void dosomething(char *s);

void myfn()
{
    char *s, *s1;
    int len;

    getstr1(buffer, 128);
    getstr2(&s);

    len = *s + *buffer;
    memcpy(buffer + *buffer + 1, s + 1, (*s) * sizeof(char));
    *buffer = len;

    dosomething(buffer);
}

使用/O2优化选项的MSVC将产生以下输出:

_s$ = -4                                                ; size = 4
void myfn(void) PROC                                 ; myfn, COMDAT
        push    ecx
        push    128                           ; 00000080H
        push    OFFSET char * buffer             ; buffer
        call    void getstr1(char *,int)           ; getstr1
        lea     eax, DWORD PTR _s$[esp+12]
        push    eax
        call    void getstr2(char * *)                    ; getstr2
        mov     eax, DWORD PTR _s$[esp+16]
        push    OFFSET char * buffer             ; buffer
        mov     al, BYTE PTR [eax]
        add     BYTE PTR char * buffer, al
        call    void dosomething(char *)              ; dosomething
        add     esp, 20                             ; 00000014H
        ret     0
void myfn(void) ENDP                                 ; myfn

您可以在Godbolt上检查这个:https://godbolt.org/z/m9yH0C

为什么编译器省略了memcpy调用?有趣的是,将外部变量声明为“extern char buffer[N];”(其中N >= 2)或“extern char *buffer;”会使编译器使用memcpy。同时,将memcpy替换为memmove也会产生同样的效果。我知道当源区域与目标区域重叠时可能会出现未定义行为,但在这里编译器并不知道这一点。


2
extern char buffer[]; 合法吗? - NathanOliver
1
@Ctx 是正确的,但是无论如何 sizeof buffer 都没有太多意义。 - Jabberwocky
1
我尝试了C和C++:结果是一样的。最初它是C代码。更多细节:这个问题可以在MSVC 2005和现代编译器中复现(其中一些可以在godbolt.org上检查)。 - oleque
2
C或C++很重要,因为C++有一个名为“memory”的标准头文件,但是C没有这样的东西,除了我相信一些非标准的Linux头文件。也许还有一些名为memory.h的MS垃圾?我在MSDN中找不到它。我的意思是你没有包含string.h,这是memcpy所必需的。VS最多只能符合C90标准,可能会疯狂地假设原型是“int memcpy(int, int, int);”,因为他们可能也不遵循C99。真的很难猜测这个所谓的“编译器”做了什么和没做什么。 - Lundin
2
extern char buffer[];合法的。此外,它与 extern char *buffer; 完全不同 - Antti Haapala -- Слава Україні
显示剩余19条评论
2个回答

11

我认为这是 MSVC 的一个 bug,因为你所做的是合法的。

请注意,已经有一个类似的 bug 报告,标题为:Release build with speed optimize leaves an array uninitialized

Bug 报告中提供了用于重现问题的代码,也使用了 extern type array[];

据团队表示,该问题在即将发布的版本中得到了修复(未说明具体版本)。


“好的”,而“临时权宜之计”是修改所有C源代码。 - Antti Haapala -- Слава Україні
2
看起来他们在没有任何人注意到的情况下就存在了这个漏洞12年,这似乎不太可能? - Lundin
@Lundin 或者他们不在意? - Eugene Sh.
是的,看起来是同一个 bug,但他们说 VS2015 生成了正确的输出。在 godbolt 网站上你可以尝试两个版本,结果是一样的(缺少 memcpy 调用)。 - oleque
即将发布的版本中修复。不确定是指VS2019还是其中一个VS2017版本。我不会期望任何旧编译器被修复。 - drescherjm
看起来确实像是 MSVC 的一个 bug。解决方法是指定外部数组大小,例如:"extern char buffer[BUFFER_SIZE];" - oleque

2
你所做的是完全合法的,在MSVC中肯定存在一个错误。
以下是提交错误报告的简化版本:
"你可以尝试下列步骤来提交错误报告:"
#include <string.h>

extern unsigned char buffer[], *s;

void myfn() {
    memcpy(buffer + *buffer + 1, s + 1, *s);
    *buffer = 1;
}

编译成:

void myfn(void) PROC                                 ; myfn, COMDAT
        mov     BYTE PTR unsigned char * buffer, 1
        ret     0
void myfn(void) ENDP                                 ; myfn

删除语句*buffer = 1;可以解决代码生成错误问题。
Godbolt的编译器探索器上进行检查。


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