C中使用字符串常量初始化数组的机制

C中使用字符串常量初始化数组的机制,c,arrays,c-strings,C,Arrays,C Strings,该定义是否: char arr_of_chars[] = "hello world"; 在内存中的某个位置创建一个常量字符数组(以null结尾),然后将该数组的内容复制到arr\u of_chars,还是直接将其分配到arr\u of_chars 到底是什么机制在这里起作用 “…在内存中的某个位置创建一个常量字符数组(以null结尾),然后将该数组的内容复制到arr_of_chars” 的确如此。字符串literal“hello world”存储在程序的.rodata部分的某个位置,除非编译器

该定义是否:

char arr_of_chars[] = "hello world";
在内存中的某个位置创建一个常量字符数组(以null结尾),然后将该数组的内容复制到
arr\u of_chars
,还是直接将其分配到
arr\u of_chars

到底是什么机制在这里起作用

“…在内存中的某个位置创建一个常量字符数组(以null结尾),然后将该数组的内容复制到arr_of_chars”


的确如此。字符串literal
“hello world”
存储在程序的
.rodata
部分的某个位置,除非编译器设法完全优化它(取决于数组的范围)。从那里它被复制到您的数组中

这将在常量段中创建以null结尾的字符串
hello world\0

在main函数中,该字符串将被复制到字符数组中

让我突出显示汇编输出中的几行,以澄清这一点

PUBLIC  ??_C@_0M@LACCCNMM@hello?5world?$AA@
这将创建一个公共令牌

CONST   SEGMENT
??_C@_0M@LACCCNMM@hello?5world?$AA@ DB 'hello world', 00H
CONST   ENDS
这会将以null结尾的常量字符串分配给令牌

lea rax, QWORD PTR arr_of_chars$[rbp]
lea rcx, OFFSET FLAT:??_C@_0M@LACCCNMM@hello?5world?$AA@
mov rdi, rax      ; Set destination to stack location
mov rsi, rcx      ; Set source to public token
mov ecx, 12       ; Set counter to number of times to repeat
rep movsb         ; Copy single byte from source to destination and increment locations

这将设置源和目标,并逐字符复制12次,即“hello world”和空终止符的长度。目标是堆栈上的一个位置,源是公共令牌。

您所要求的不是由C指定的。简而言之,C是根据抽象机器及其可观察的行为指定的。在本例中,这意味着您只知道有一个数组变量
arr\u of_chars
是从字符串文本初始化的

CONST   SEGMENT
??_C@_0M@LACCCNMM@hello?5world?$AA@ DB 'hello world', 00H
CONST   ENDS

当谈到段、复制等时,您已经谈到了C的具体实现以及它们在做什么。假设您的
arr\u of_chars
在文件范围内,并且给定了一个目标机器/系统,该机器/系统知道带有数据段的二进制文件,C编译器可以将初始化的数组直接放在数据段中——可观察的行为与运行时首先将字节复制到数组的方法没有什么不同。

它是字符串在C中存储的主题

字符串可以通过以下方式存储:

  • 字符串作为字符数组
  • 使用字符指针的字符串
  • 当字符串声明为字符数组时,它们会像C中其他类型的数组一样存储。例如,如果str[]是自动变量,则字符串存储在堆栈段中,如果它是全局或静态变量,则存储在数据段中

    在使用字符指针存储字符串的情况下,可以通过两种方式进行存储,

  • 共享段中的只读字符串

  • 在上面的一行中,“Hello_World”存储在共享的只读位置,但指针str存储在读写内存中。您可以将str更改为指向其他对象,但不能更改当前str的值。因此,只有当我们不想在程序的后期修改字符串时,才应使用这种字符串

  • 在堆段中动态分配

    char *str = NULL;
    int size = 6;
    
    str = (char *) malloc(sizeof(char)*size);
    
    *(str+0) = 'H'; 
    *(str+1) = 'E';  
    *(str+2) = 'L';  
    *(str+3) = 'L';  
    *(str+4) = 'O';  
    *(str+5) = '\0';
    

  • hello world
    是放置在系统中某处的以null结尾的字符串。初始化变量时,字符串被复制到RAM。如果变量是全局的或静态的,则初始化也是如此,并且在运行时不会进行复制。如果变量是自动的,则初始化是动态的,每次实例化变量时都会执行初始化。@TomKarzes我不相信这一点。考虑这一点:<代码> const char A[] =“Hello World”;字符b[]=“你好,世界”。您有两个精确的值,即使它们是全局值或静态值,在.rodata部分中也只有一个
    hello world
    内存存储,特别是在嵌入式系统上。它将首先在运行时复制到非常量变量。@tilz0R这完全由编译器决定。什么“更好”取决于目标系统和确切的要求。@在这种情况下,
    b
    通常与相应的初始化值一起放置在数据部分。初始化将作为程序加载到内存的结果发生。这是一般规则中唯一的例外,因为它复制字符串本身,而不是像通常那样复制指针:)。这里没有任何例外。这是一个初始化,不是赋值。什么是
    .rodata
    ?-)@JensGustedt尽管C标准对计算机内存和段一无所知,但现实世界中对段有行业标准的命名约定
    .rodata
    将是只读数据段,由流行的链接文件格式使用。可以找到行业标准内存段的摘要和说明。检查一个具体实现的行为如何回答这个问题?@FelixPalmen-你是对的。我所发布的只是一个观察。但我不确定标准是否定义了执行此操作的方法。但是,该操作是使用字符串文本初始化的数组。因此字符串文字必须存储在某个地方,并且必须在初始化期间复制到数组中。标准没有指定它,这是我的观点;)不,不一定要将字符串文本存储在某个地方。这完全取决于目标的二进制格式和编译器的决定,如果这个确切的字符串文字仅用于初始化这个变量,那么“消除”它并将初始化的数组放入数据段可能是一个有效的决定——例如:)我同意。因此,我想我还将对其他一些著名的C编译器进行一些观察。:)确切地如果字符串很短,初始化的另一种可能性是使用硬件寄存器或立即数,并且根据
    char
    数组的使用情况,根本不在内存中表示它。
    char *str = NULL;
    int size = 6;
    
    str = (char *) malloc(sizeof(char)*size);
    
    *(str+0) = 'H'; 
    *(str+1) = 'E';  
    *(str+2) = 'L';  
    *(str+3) = 'L';  
    *(str+4) = 'O';  
    *(str+5) = '\0';