使用字符串字面量初始化数组时,不同的汇编代码会被生成。

4
根据这个帖子,使用较短的字符串文本初始化数组会用零填充数组。
那么,当编译为ARM Cortex M4时,这两个函数(test1test2)产生不同结果的原因是什么?
extern void write(char * buff);

void test1(void)
{
    char buff[8] = {'t', 'e', 's', 't', 0, 0, 0, 0 };
    write(buff);
}

void test2(void)
{
    char buff[8] = "test";
    write(buff);
}

我在x86-64上获得了相同的汇编代码,但在ARM gcc上却获得不同的结果

test1:
        str     lr, [sp, #-4]!
        sub     sp, sp, #12
        mov     r3, sp
        ldr     r2, .L4
        ldm     r2, {r0, r1}
        stm     r3, {r0, r1}
        mov     r0, r3
        bl      write
        add     sp, sp, #12
        ldr     pc, [sp], #4
.L4:
        .word   .LANCHOR0
test2:
        mov     r3, #0
        str     lr, [sp, #-4]!
        ldr     r2, .L8
        sub     sp, sp, #12
        ldm     r2, {r0, r1}
        str     r0, [sp]
        mov     r0, sp
        strb    r1, [sp, #4]
        strb    r3, [sp, #5]
        strb    r3, [sp, #6]
        strb    r3, [sp, #7]
        bl      write
        add     sp, sp, #12
        ldr     pc, [sp], #4
.L8:
        .word   .LANCHOR0+8

2
就C标准而言,这些是等价的。尽管编译器可以跳过在空终止符后写零,如果它可以推断出\0之后的内存没有被使用(由于外部链接,在这里它不能),因此似乎这是某种错误。如果我将“test2”更改为“char buff [8] =“ test \ 0 \ 0 \ 0”;”(4个尾随零),我在ARM上的两种情况下都获得相同的机器代码。 - Lundin
顺便提一下:如果你使用{'t','e','s','t',0},buff也会被填满0。我期望这将产生与字符串字面值版本相同的汇编输出。 - Stephan Lechner
@StephanLechner:[它可以](https://godbolt.org/z/WOeBbD),除了使用arm gcc编译时。 - Lou
1
x86-64是一个64位体系结构(并免费将32位立即数零扩展为64位),因此,出于多种原因,这两个版本使用单个qword存储不足为奇。如果您使数组长度为9字节,并添加第5个非零字节(https://godbolt.org/z/Xk-vLP),则gcc会生成明显不同的代码(带有一些愚蠢的test1代码),但clang仍然为两者生成相同的良好代码。 - Peter Cordes
1个回答

3

首先,就内存内容而言,命名为buf的对象所构成的代码是等效的。

尽管如此,编译器显然会针对第二个函数生成更糟糕的代码。因此,由于有一种方法可以生成更优化但语义相等的代码,因此将其视为编译器优化失败并报告错误是合理的。

如果编译器想要发出相同的代码,它必须认识到内存中的字符串文字表示形式可以进行零填充而不改变程序的语义(尽管字符串字面值本身不能填充,因为sizeof "test"不能等于sizeof "test\0\0\0")。

然而,由于这只有在使用字符串字面值初始化显式长度数组(通常是一个坏主意),而该数组比字面值长且普通字符串语义不足以描述(空终止符之后的字节是相关的)时才具有优势,因此在这种情况下更好的优化价值似乎有限。

附加说明:如果将godbolt设置为不删除汇编指令,您可以看到如何创建文本字符串:

.section        .rodata
.align  2
.set    .LANCHOR0,. + 0
.byte   116
.byte   101
.byte   115
.byte   116
.byte   0
.byte   0
.byte   0
.byte   0
.ascii  "test\000"
.space  3

有趣的是,编译器并没有进行去重处理,并且将字符串字面量后的填充留给汇编程序(.space 3)来处理,而不是明确地将其清零。

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