簡體   English   中英

為什么gcc為memcpy復制rodata字符串? 怎么避免呢?

[英]Why gcc duplicates rodata string for memcpy? How to avoid it?

由於某種原因,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才能使用原始字符串。

您觀察到的行為由標准明確指定。

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

你有一個變量模式的聲明和一個字符串文字形式的初始化程序。 該標准第6.4.5 / 6段規定

在轉換階段7中,將值為零的字節或代碼附加到由字符串文字或文字產生的每個多字節字符序列。 然后使用多字節字符序列初始化靜態存儲持續時間和長度的數組,該數組足以包含序列。

(強調補充。)結果數組具有靜態存儲持續時間意味着,至少原則上,必須在程序中為它保留存儲器。 這是您在str1.1中看到的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秒來完成那個。 但是,如果你這樣做,不要忘記添加一個顯式的字符串終止符,如上所述。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM