【问题标题】:In which data segment is the C string stored?C 字符串存储在哪个数据段中?
【发布时间】:2016-06-18 23:17:22
【问题描述】:

我想知道char s[] = "hello"char *s = "hello" 之间有什么区别。

看了thisthis,这个问题我还是不是很清楚。


据我所知,内存中有五个数据段,Text、BSS、Data、Stack和Heap。

据我了解,

如果是char s[] = "hello"

  1. "hello" 在文本中。
  2. s 如果是全局变量,则在 Data 中;如果是局部变量,则在 Stack。

  3. 我们还有一个"hello"的副本,其中存储了s,所以我们可以通过s修改这个字符串的值。

如果是char *s = "hello":

  1. "hello" 在文本中。
  2. s 如果是全局变量,则在 Data 中;如果是局部变量,则在 Stack 中。
  3. s 只是指向 Text 中的 "hello",我们没有它的副本,因此通过该指针修改字符串的值会导致“分段错误”。

我说的对吗?

【问题讨论】:

  • 我不确定,但我不会像您所说的那样称呼“数据段”。据我所知,分段或分段是一种在逻辑上组织内存的方式,以及“分页”的其他常见变体......
  • 通常没有堆栈或堆段。 C 甚至没有提到“段”或“部分”。这取决于您的实施。
  • @nbro:C 标准中没有定义。
  • @Olaf 我认为数据和代码段的概念对于理解内部发生的事情很重要。但我们也应该将它们归类为可变值和不可变值。
  • @shuva 其实这是我在一家以C为主语言的著名公司遇到的一个面试题。我知道 char 数组是可变的,而另一个不是。但是当面试官问我字符串存储在哪里时,我并不清楚。这就是我发布这个问题的原因。谢谢你的回答。

标签: c string char


【解决方案1】:

回答你的第一个问题:

char s[] = "hello";

schar 类型的数组。数组是const 指针,这意味着您不能使用指针算法(即s++)更改s。不过,数据不是 const,因此您可以更改它。
请参阅此示例 C 代码:

#include <stdio.h>

void reverse(char *p){
    char c;
    char* q = p;
    while (*q) q++; 
    q--; // point to the end
    while (p < q) {
        c = *p;
        *p++ = *q;
        *q-- = c;
    }
}

int main(){
    char s[]  = "DCBA";
    reverse( s);
    printf("%s\n", s); // ABCD
}

反转文本"DCBA" 并生成"ABCD"

char *p = "hello"

p 是一个指向字符的指针。您可以进行指针运算 -- p++ 将编译 -- 并将数据放入内存的只读部分(常量数据)。
并且使用p[0]='a'; 会导致运行时错误:

#include <stdio.h>
int main(){
    char* s  = "DCBA";  
    s[0]='D'; // compile ok but runtime error
    printf("%s\n", s); // ABCD
}  

这会编译,但不会运行。

const char* const s = "DCBA";

使用const char* const,您既不能更改s,也不能更改指向的数据内容(即"DCBE")。所以数据和指针都是常量:

#include <stdio.h>
int main(){
    const char* const s  = "DCBA";  
    s[0]='D'; // compile error
    printf("%s\n", s); // ABCD
}

文本段通常是存储代码的段,为const;即不可更改。在嵌入式系统中,这是 ROM、PROM 或闪存;在台式计算机中,它可以在 RAM 中。

堆栈是用于函数中局部变量的 RAM 内存。

堆是用于全局变量和堆初始化数据的 RAM 内存。

BSS 包含所有初始化为零或未初始化的变量的全局变量和静态变量。

有关详细信息,请参阅the relevant Wikipediathis relevant Stack Overflow question

关于s 本身:编译器决定将它放在哪里(在堆栈空间或 CPU 寄存器中)。

有关内存保护和访问冲突或分段错误的更多信息,请参阅the relevant Wikipedia page

这是一个非常广泛的话题,最终确切的答案取决于您的硬件和编译器。

【讨论】:

  • 对于char*s = "hello""hello" 由 gcc 汇编器保存在只读部分 .rodata。似乎"hello" 这里是 immutable 与解释中的建议相反。当s 引用"hello" 时,s[0] = 1; 会导致分段错误。同样s 不是const。它可以引用 runtime 上的 mutable 字符串。所以s[0] 也可能不会导致分段错误。
  • 在您提供的wiki页面link中,有一个char string[] = "Hello World";的例子。它说“这些变量的值最初存储在只读内存中(通常在 .text 中),并在程序启动例程期间复制到 .data 段中。”
  • 很好的例子。 Const 对于让编译器检测潜在的“分段错误”非常有用。面试官介绍的方法和你一样。
【解决方案2】:

你是对的,第一种情况的“hello”是 mutable 而第二种情况是 immutable 字符串。它们在初始化之前保存在只读存储器中。

在第一种情况下,可变内存是从不可变字符串初始化/复制。在第二种情况下,指针指向不可变字符串

对于第一种情况,维基百科说,

这些变量的值最初存储在 只读存储器(通常在 .text 中)并被复制到 程序启动过程中的.data段。

让我们检查 segment.c 文件。

char*s = "hello"; // string
char sar[] = "hello"; // string array
char content[32];

int main(int argc, char*argv[]) {
        char psar[] = "parhello"; // local/private string array
        char*ps = "phello"; // private string
        content[0] = 1;
        sar[3] = 1; // OK
        // sar++; // not allowed
        // s[2] = 1; // segmentation fault
        s = sar;
        s[2] = 1; // OK
        psar[3] = 1; // OK
        // ps[2] = 1; // segmentation fault
        ps = psar;
        ps[2] = 1; // OK
        return 0;
}

这是为segment.c 文件生成的程序集。请注意,ssar 都在 global aka .data 段中。似乎 sarconst pointer 到一个 mutable initialized 内存或根本不是指针(实际上它是一个数组)。最终它暗示sizeof(sar) = 6sizeof(s) = 8 不同。 readonly(.rodata) 部分中有“hello”和“phello”,实际上是不可变

    .file   "segment.c"
    .globl  s
    .section    .rodata
.LC0:
    .string "hello"
    .data
    .align 8
    .type   s, @object
    .size   s, 8
s:
    .quad   .LC0
    .globl  sar
    .type   sar, @object
    .size   sar, 6
sar:
    .string "hello"
    .comm   content,32,32
    .section    .rodata
.LC1:
    .string "phello"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $64, %rsp
    movl    %edi, -52(%rbp)
    movq    %rsi, -64(%rbp)
    movq    %fs:40, %rax
    movq    %rax, -8(%rbp)
    xorl    %eax, %eax
    movl    $1752326512, -32(%rbp)
    movl    $1869376613, -28(%rbp)
    movb    $0, -24(%rbp)
    movq    $.LC1, -40(%rbp)
    movb    $1, content(%rip)
    movb    $1, sar+3(%rip)
    movq    $sar, s(%rip)
    movq    s(%rip), %rax
    addq    $2, %rax
    movb    $1, (%rax)
    movb    $1, -29(%rbp)
    leaq    -32(%rbp), %rax
    movq    %rax, -40(%rbp)
    movq    -40(%rbp), %rax
    addq    $2, %rax
    movb    $1, (%rax)
    movl    $0, %eax
    movq    -8(%rbp), %rdx
    xorq    %fs:40, %rdx
    je  .L2
    call    __stack_chk_fail
.L2:
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
    .section    .note.GNU-stack,"",@progbits

同样对于 main 中的 local 变量,编译器不会费心创建 name。它可能会将其保存在 registerstack 内存中。

请注意,局部变量值“parhello”已优化为 1752326512 和 1869376613 数字。我通过将“parhello”的值更改为“parhellp”来发现它。汇编输出的diff如下,

39c39
<   movl    $1886153829, -28(%rbp)
---
>   movl    $1869376613, -28(%rbp)

所以 psar 没有单独的不可变存储。它在代码段中变成整数。

【讨论】:

  • 我对只读内存很好奇。是文本、数据还是其他?面试时,我回答字符串应该在只读存储器中。但是,面试官希望我弄清楚它是在哪个特定细分市场中。
  • @Galaxy 正如维基百科所说,只读部分通常 .text
  • 我同意这一点。它类似于此问题link 中的第三个答案。所以 Text 上应该有一个字符串,它的副本与数组存储在同一位置,通常在 Stack 上用于局部变量?
  • @Galaxy 是的,它通常应该在 Stack 上复制以作为局部变量。它将被复制到全局变量的数据段中。
  • @Galaxy 发生了一件有趣的事情,数组字符串没有作为单独的字符串存储在文本段中,而是在代码段中设置为整数。你应该看看。
猜你喜欢
  • 1970-01-01
  • 2012-07-12
  • 2021-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-06
相关资源
最近更新 更多