Почему MSVC оптимизирует этот вызов memcpy?

#c #c

#c #c

Вопрос:

У меня есть следующий код на 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(amp;s);

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

    dosomething(buffer);
}
  

MSVC с опцией оптимизации /O2 выдает следующий результат:

 _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

Почему компилятор пропустил вызов memcpy? Интересно, что объявление внешней переменной как «extern char buffer[N];» где N > = 2 или как «extern char * buffer;» заставляет компилятор использовать memcpy. Также замена memcpy на memmove делает то же самое. Я знаю о возможном UB, когда области источника и назначения перекрываются, но здесь компилятор не знает об этом.

Комментарии:

1. extern char buffer[]; Законно?

2. @Ctx верно, но в любом случае в любом случае не имеет особого смысла. sizeof buffer

3. Я пробовал как C, так и C : результат тот же. Изначально это был C-код. Подробнее: проблема может быть воспроизведена в MSVC 2005 и в современных компиляторах (некоторые из них вы можете проверить на godbolt.org ).

4. Ну, C или C имеет значение, потому что C имеет стандартный заголовок с именем memory , но C не имеет такого понятия, кроме, я полагаю, какого-то нестандартного заголовка Linux. Может быть, есть также какой-то мусор MS, называемый memory.h? Я не могу найти его в MSDN. Я имею в виду, что вы не включили string.h то, что требуется для memcpy. VS, будучи в лучшем случае едва совместимым с C90, может затем сойти с ума и предположить, что прототип является int memcpy(int, int, int); , потому что они, вероятно, также не соответствуют C99. Действительно трудно угадать, что этот так называемый «компилятор» делает, а что нет.

5. extern char buffer[]; это законно . Кроме того, это ничего подобного extern char *buffer; .

Ответ №1:

Я думаю, что это ошибка в MSVC, поскольку то, что вы делаете, является законным.

Обратите внимание, что уже была обнаружена аналогичная ошибка под названием: выпуск сборки с оптимизацией скорости оставляет массив неинициализированным.

Код, приведенный для воспроизведения проблемы в отчете об ошибке, также использует extern type array[];

Согласно команде, эта проблема исправлена в предстоящем выпуске (который не упоминается).

Комментарии:

1. … «приятно»… и «промежуточное исправление» заключается в изменении всего исходного кода C.

2. Кажется маловероятным, что у них была эта ошибка в течение 12 лет, и никто этого не заметил?

3. @Lundin Или .. им все равно?

4. Да, это похоже на ту же ошибку, но они написали, что VS2015 выдал правильный вывод. На сайте godbolt вы можете попробовать оба из них, и результат будет одинаковым (вызов memcpy отсутствует).

5. исправлено в предстоящем выпуске Не уверен, означает ли это VS2019 или один из выпусков VS2017. Я бы не ожидал, что какой-либо старый компилятор будет исправлен.

Ответ №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’s Compiler Explorer.