Почему gcc дублирует строку родата для memcpy? Как этого избежать? - PullRequest
3 голосов
/ 06 мая 2019

По какой-то причине GCC дублирует содержимое строки const char в отдельную область родата, чего я не понимаю.Я компилирую предоставленный код:

static const char pattern[] = "[SOME TEST PATTERN TO CALCULATE SUM FROM] ";

static char tmpbuf[sizeof(pattern) + 1];

uint16_t sum(char *buf, int size)
{
    uint16_t ret = 0;

    for(int i = 0; i < size; ++i)
        ret += buf[i];

    return ret;
}

void getPattern(char **retbuf)
{
    memcpy(tmpbuf, pattern, sizeof(tmpbuf) -1);
    *retbuf = tmpbuf;
}

int main(int argc, char *argv[])
{
    getPattern(&argv[0]);

    return sum((char *)pattern, sizeof(pattern) - 2) > 0;
}

void _exit(int status)
{
    while(1)
    {
        asm("nop");
    }
}

с помощью компилятора arm gcc, используя команду:

arm-none-eabi-gcc -Os dbstr.c -o dbstr -Wl,-Map,"dbstr.map" -fdata-sections

В результирующем двоичном файле, даже если он удален, я нахожу строку:

"[SOME TEST PATTERN TO CALCULATE SUM FROM] "

продублировано.

Глядя на карту символов, я нахожу:

.rodata.pattern
                0x000087d8       0x2b ... ccumYoyx.o
.rodata.str1.1
                0x00008803       0x2b ... ccumYoyx.o
and
.bss.tmpbuf    0x00018ca0       0x2c ... ccumYoyx.o

символ "pattern" - это исходный массив символов, символ "str1" - дубликат, а символ "tmpbuf" - целевой буфер., в который я хочу скопировать "pattern".

Просматривая сгенерированную сборку, я обнаружил, что memcpy использует дубликат, созданный компилятором:

getPattern:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
->  ldr r3, .L6
    push    {r4, lr}
    mov r2, #43
    mov r4, r0
    ldr r1, .L6+4
    mov r0, r3
    bl  memcpy
...

.L6:
    .word   .LANCHOR0
->  .word   .LC0
...
pattern:
    .ascii  "[SOME TEST PATTERN TO CALCULATE SUM FROM] \000"
    .section    .rodata.str1.1,"aMS",%progbits,1
.LC0: /*duplicate string*/
    .ascii  "[SOME TEST PATTERN TO CALCULATE SUM FROM] \000"
    .ident  "GCC: (GNU Tools for Arm Embedded Processors 8-2018-q4-major) 8.2.1 20181213 (release) [gcc-8-branch revision 267074]"

Я проверил, что это происходит с arm-none-eabi-gcc версии с 6-2017-q1-обновление до 8-2018-q4-major (последняя версия доступна на developer.arm.com).

Я также пытался использовать различные оптимизации.Дублирование не происходит только при использовании -O0.Для других это так.

В более крупном приложении, когда возникла эта проблема, оказалось, что memcpy скопировал дублирующую строку вместо оригинальной - это было определено заменой исходной строки в двоичном.Мне нужна memcpy, чтобы использовать оригинальную строку.

1 Ответ

3 голосов
/ 06 мая 2019

Поведение, которое вы наблюдаете, явно указано стандартом.В

static const char pattern[] = "[SOME TEST PATTERN TO CALCULATE SUM FROM] ";

у вас есть объявление переменной pattern и инициализатор в виде строкового литерала. В параграфе 6.4.5 / 6 стандарта указано, что

На этапе 7 преобразования байт или код нулевого значения добавляются к каждой последовательности многобайтовых символов, которая получается из строкибуквальное или буквальное. Последовательность многобайтовых символов затем используется для инициализации массива статической длительности хранения и длины, достаточной только для размещения последовательности.

(выделение добавлено).То, что результирующий массив имеет статическую длительность хранения, означает, что, по крайней мере, в принципе, для него должна быть зарезервирована память в программе.Это то, что вы видите в форме str1.1.Но вы также используете эту строку для инициализации массива, так что массив получает ту же последовательность символов, которая также занимает память в двоичном файле, потому что она также имеет статическую продолжительность хранения в результате объявления в области видимости файла.

В принципе, GCC должен иметь возможность оптимизировать дополнительный массив.В частности, опция -fmerge-constants должна делать это, но она включена на всех уровнях оптимизации, кроме -O0, поэтому удивительно, что вы не видите такого объединения, но возможно, что объединение будет выполнено во время соединения,так что то, что вы видите, является бессмысленным артефактом просмотра объектного файла перед компоновкой.

Вы также сможете избежать копирования, объявив pattern указателем вместо массива:

static const char * const pattern = "[SOME TEST PATTERN TO CALCULATE SUM FROM] ";

, но NOTE WELL , что хотя результат может использоваться многими из тех же способов, что и версия массива, он не является семантически идентичным.Вы увидите разницу, если вы примените оператор sizeof, *, или &, или _Alignof к pattern.


Обновление:

Другой,гораздо страшнее, обходным путем было бы вообще избежать строкового литерала, например так:

static const char pattern[] = {
        '[', 'S', 'O', 'M', 'E', ' ', 'T', 'E', 'S', 'T', ' ', 'P', 'A', 'T',
        'T', 'E', 'R', 'N', ' ', 'T', 'O', ' ', 'C', 'A', 'L', 'C', 'U', 'L',
        'A', 'T', 'E', ' ', 'S', 'U', 'M', ' ', 'F', 'R', 'O', 'M', ']', ' ', '\0' };

Это оставляет вас с pattern в качестве массива, а не указателя и без отдельного массива для строкового литерала.Это некрасиво и сложнее в обслуживании, но преобразовать строковую буквальную форму в такую ​​несложно - мне потребовалось около 30 секунд, чтобы это сделать.Однако, если вы сделаете это, не забудьте добавить явный терминатор строки, как указано выше.

...