C语言 将一个字符串字面量复制到一个字符数组中,如何将字符串字面量复制到堆栈上?

hrysbysz  于 2023-10-16  发布在  其他
关注(0)|答案(4)|浏览(123)

我知道当你执行char array[] =“string”时,字符串字面量“string”会从数据段复制到堆栈。字符串是一个字符接一个字符地复制吗?或者编译器获取字符串字面量的开始和结束地址,并将整个字符串一次复制到堆栈?
谢谢

70gysomp

70gysomp1#

编译器做任何它“想”做的事情,只要观察到的结果是相同的。有时候根本没有副本。
C标准没有指定如何进行复制,因此C实现可以通过任何方式自由地实现结果。C标准强加的唯一要求是可观察的结果(例如写入标准输出的文本)必须是定义的。
当工程师设计一个高质量的C实现时,他们会花一些时间考虑在这种情况下复制字符串的最佳方法,他们会寻求设计一个在每种情况下选择最佳方法的编译器。一个短字符串可以通过使用“移动立即值”指令来构建。长字符串可以通过调用memcpy来复制。一个中间字符串可以通过一个对memcpy的内联调用来复制,实际上是几个指令,每个指令移动几个字节。
当工程师设计一个廉价的C实现时,一些只是完成工作的东西,这样代码就可以移植到机器上,但不需要太快,他们会做任何对他们来说最简单的事情。
有时编译器根本不会复制字符串:如果编译器可以告诉你不需要一个副本,那么就没有理由复制一个副本。例如,如果编译器发现您只是将字符串传递给printf,而根本没有修改它,那么编译器将通过将原始字符串传递给printf来获得相同的结果,而不进行复制。

zpqajqem

zpqajqem2#

完全没有理由认为有复制品。
以下面的代码为例。

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'的ascurry值。字符串根本没有数据段表示,数组也不是通过一个字符接一个字符地复制任何东西来初始化的,或者通过任何其他聪明的复制方法来初始化。
当然,这只是一个编译器的实现(g++ 4.8),其他编译器可以做任何他们想做的事情,只要符合语言规范。

iugsix8n

iugsix8n3#

这取决于编译器和目标体系结构。
可能有非常简单的目标架构,如微控制器,它没有支持复制内存块的指令。可能存在为教学设计的非常简单的编译器,即使在支持更有效方法的体系结构上也会生成逐字节复制。
但是,您可以假设生产级编译器会做合理的事情,并在这种情况下为大多数流行的体系结构生成最快的代码,并且您真的不需要担心它。
不过,最好的检查方法是读取编译器生成的程序集。
以下面的测试代码(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

这里,“repmovsb”是将字符串复制到堆栈的指令。
下面是一个ARMv 4程序集的摘录(可能更容易阅读):

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的程序集,但我希望它使用可用的最快方法之一。

13z8s7eq

13z8s7eq4#

我不太清楚你所说的“逐字符”和“整个字符串”复制方法之间的区别是什么意思。字符串通常不是机器级实体,这意味着它不可能被复制为“整个字符串”。你觉得这怎么可能发生?
字符串将始终被“逐个字符”复制,至少在概念上是这样。现在,当涉及到复制扩展内存区域时,编译器可以通过尽可能执行逐字(而不是逐字节)复制来优化复制过程。类似的优化可以在处理器微体系结构级实现。
但无论如何,在一般情况下,复制是作为一个 * 迭代 * 过程实现的,而不是作为对“整个字符串”的一些原子操作。
最重要的是,一个聪明的编译器可能会意识到,在某些情况下,复制是没有必要的。例如,如果您的代码不修改array对象,并且不依赖于其地址标识,则编译器可能会直接决定使用原始字符串字面量,而根本不进行任何复制(即基本上是悄悄地用const char *array = "string"替换您的char array[] = "string"

相关问题