为什么gcc为memcpy复制rodata字符串?如何避免呢?

时间:2019-05-06 11:08:28

标签: c string gcc duplicates memcpy

由于某些原因,GCC将const char字符串的内容复制到单独的rodata区域中,我不理解。 我编译提供的代码:

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

符号“模式”是原始数组 符号“ str1”重复 符号“ tmpbuf”是目标缓冲区,我要将“模式”复制到其中。

查看生成的程序集,我发现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-update到8-2018-q4-major(最新可在developer.arm.com上找到)的情况。

我也尝试使用各种优化。仅当使用-O0时,才不会重复。对于其他人而言。

在更大的应用程序中,发生了此问题,结果是memcpy复制了重复的字符串而不是原始字符串-它是通过​​将原始字符串替换为二进制文件来确定的。我需要使用memcpy才能使用原始字符串。

1 个答案:

答案 0 :(得分:3)

您观察到的行为由标准明确指定。在

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

您有一个变量模式声明和一个字符串文字形式的初始化程序。标准的Paragraph 6.4.5/6规定了

  

在翻译阶段7中,将值零的字节或代码附加到   字符串文字产生的每个多字节字符序列   或文字。 然后将多字节字符序列用于   初始化静态存储持续时间的数组 和长度   足以包含序列。

(添加了强调。)结果数组具有静态存储持续时间,这意味着,至少在原则上,必须在程序中为其保留内存。这就是str1.1形式的内容。但是,您还使用该字符串初始化数组,以使该数组获得相同的字符序列,并且由于在文件范围内声明该数组也具有静态存储持续时间,因此也占用了二进制文件中的内存。

原则上,GCC应该能够优化多余的阵列。特别是,选项-fmerge-constants应该执行此操作,但是它包含在-O0以外的所有优化级别中,因此令人惊讶的是您没有看到这种合并,但是很可能会执行合并在链接时,所以您看到的是在链接之前查看目标文件的毫无意义的产物。

您还应该通过声明pattern作为指针而不是数组来避免复制:

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

注意,尽管结果可以用与数组版本相同的许多方式使用,但在语义上并不相同。如果将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秒钟来完成这个任务。不过,如果要这样做,请不要忘记像上面那样添加一个显式的字符串终止符。