我知道当您执行 char array[] = "string" 时,字符串文字 "string" 会从数据段复制到堆栈中。字符串文字是否逐个字符地复制?还是编译器获取字符串文字的开始和结束地址,并一次性将整个字符串复制到堆栈中?
谢谢
只要观察到的结果相同,编译器就会做它“想要”做的任何事情。有时根本没有副本。
C 标准没有规定如何进行复制,因此 C 实现可以通过任何方式自由地实现结果。C 标准强加的唯一要求是可观察的结果(例如写入标准输出的文本)必须符合定义。
当工程师设计一个高质量的 C 实现时,他们会花一些时间考虑在这种情况下复制字符串的最佳方法,并且他们会寻求设计一个在每种情况下选择最佳方法的编译器。可以使用“移动立即值”指令来构建一个短字符串。一个长字符串可以通过调用来复制memcpy
。中间字符串可以通过对 的内联调用来复制memcpy
,实际上是一些移动几个字节的指令。
当工程师正在设计一个廉价的 C 实现时,只要完成工作以便将代码移植到机器上但不需要很快,他们就会做任何对他们来说最容易的事情。
有时编译器根本不会复制字符串:如果编译器可以告诉您不需要副本,则没有理由进行复制。例如,如果编译器看到您只是将字符串传递给printf
而根本不修改它,那么编译器会得到相同的结果,而无需通过将原始字符串传递给printf
.
根本没有理由认为有副本。
以下面的代码为例。
int main() {
char c[] = "hi";
}
对我来说,这会产生(未优化的)程序集:
main:
pushq %rbp
movq %rsp, %rbp
movw $26984, -16(%rbp)
movb $0, -14(%rbp)
movl $0, %eax
popq %rbp
ret
数组的内存通过将其设置为值 26984 来初始化。这个值恰好由两个字节 0x68 和 0x69 表示,它们是 'h' 和 'i' 的 ascii 值。根本没有字符串的数据段表示,并且数组不是通过将任何内容逐个字符复制到其中或通过任何其他巧妙的复制方法来初始化的。
当然这只是一个编译器的实现(g++ 4.8),其他编译器可以做任何他们想做的事情,只要符合语言规范。
我不确定“逐个字符”和“整个字符串”复制方法之间的区别是什么意思。字符串通常不是机器级实体,这意味着它不可能被复制为“整个字符串”。你预计这会如何发生?
至少在概念上,字符串将始终“逐个字符”地复制。现在,当涉及到复制扩展内存区域时,编译器可以通过执行逐字(而不是逐字节)复制来优化复制过程。类似的优化可以在处理器微架构级别实现。
但无论如何,在一般情况下,复制是作为一个迭代过程来实现的,而不是作为对“整个字符串”的一些原子操作。
最重要的是,一个聪明的编译器可能会意识到在某些情况下复制根本没有必要。例如,如果您的代码不修改array
对象并且不依赖于其地址标识,编译器可能会简单地决定直接使用原始字符串文字,而不进行任何复制(即基本上悄悄地替换您的char array[] = "string"
with const char *array = "string"
)
这取决于编译器和目标架构。
可能有非常简单的目标架构,例如微控制器,它们没有支持复制内存块的指令。可能存在为教学设计的非常简单的编译器,即使在支持更有效方法的体系结构上也可以生成逐字节复制。
但是,您可以假设在这种情况下,生产级编译器会为最流行的体系结构做合理的事情并生成可能最快的代码,而您真的不需要担心。
不过,最好的检查方法是阅读编译器生成的程序集。
拿这个测试代码(stack_array_init.c):
#include <stdio.h>
int
main()
{
char a[]="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed\n"
"do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n";
printf("%s", a);
return 0;
}
并使用 gcc 将其编译为程序集,并优化大小(以减少阅读量),如下所示:
gcc -Os -S stack_array_init.c
这是 x86-64 的输出:
.file "stack_array_init.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC1:
.string "%s"
.LC0:
.string "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed\ndo eiusmod tempor incididunt ut labore et dolore magna aliqua.\n"
.section .text.startup,"ax",@progbits
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
subq $136, %rsp
.cfi_def_cfa_offset 144
movl $.LC0, %esi
movl $126, %ecx
leaq 2(%rsp), %rdi
xorl %eax, %eax
rep movsb
leaq 2(%rsp), %rsi
movl $.LC1, %edi
call printf
xorl %eax, %eax
addq $136, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.7.2-5) 4.7.2"
.section .note.GNU-stack,"",@progbits
这里,“rep movsb”是将字符串复制到堆栈的指令。
这是 ARMv4 程序集的摘录(可能更容易阅读):
main:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 128
@ frame_needed = 0, uses_anonymous_args = 0
str lr, [sp, #-4]!
sub sp, sp, #132
mov r2, #126
ldr r1, .L2
mov r0, sp
bl memcpy
mov r1, sp
ldr r0, .L2+4
bl printf
mov r0, #0
add sp, sp, #132
ldr lr, [sp], #4
bx lr
.L3:
.align 2
.L2:
.word .LC0
.word .LC1
.size main, .-main
.section .rodata.str1.4,"aMS",%progbits,1
.align 2
.LC1:
.ascii "%s\000"
.space 1
.LC0:
.ascii "Lorem ipsum dolor sit amet, consectetur adipisicing"
.ascii " elit, sed\012do eiusmod tempor incididunt ut labor"
.ascii "e et dolore magna aliqua.\012\000"
.ident "GCC: (Debian 4.6.3-14) 4.6.3"
.section .note.GNU-stack,"",%progbits
根据我对 ARM 程序集的理解,这看起来像是代码调用 memcpy 将字符串复制到堆栈数组中。虽然这没有显示 memcpy 的程序集,但我希望它使用最快的可用方法之一。