繁体   English   中英

char s[] 和 char *s 有什么区别?

[英]What is the difference between char s[] and char *s?

在 C 中,可以在这样的声明中使用字符串文字:

char s[] = "hello";

或者像这样:

char *s = "hello";

那么区别是什么呢? 我想知道在编译和运行时的存储持续时间方面实际发生了什么。

这里的区别在于

char *s = "Hello world";

将把"Hello world"放在内存只读部分,并使s成为一个指针,使得对该内存的任何写操作都是非法的。

做的时候:

char s[] = "Hello world";

将文字字符串放在只读内存中,并将字符串复制到堆栈上新分配的内存中。 从而使

s[0] = 'J';

合法的。

首先,在函数参数中,它们完全等效:

void foo(char *x);
void foo(char x[]); // exactly the same in all respects

在其他上下文中, char *分配一个指针,而char []分配一个数组。 你问,在前一种情况下,字符串去哪里了? 编译器秘密分配一个静态匿名数组来保存字符串文字。 所以:

char *x = "Foo";
// is approximately equivalent to:
static const char __secret_anonymous_array[] = "Foo";
char *x = (char *) __secret_anonymous_array;

请注意,您绝不能尝试通过此指针修改此匿名数组的内容; 影响未定义(通常意味着崩溃):

x[1] = 'O'; // BAD. DON'T DO THIS.

使用数组语法直接将其分配到新内存中。 因此修改是安全的:

char x[] = "Foo";
x[1] = 'O'; // No problem.

但是,该数组的生命周期仅与其包含的范围一样长,因此,如果您在函数中执行此操作,请不要返回或泄漏指向该数组的指针 - 使用strdup()或类似方法创建一个副本。 如果数组是在全局范围内分配的,当然没问题。

本声明:

char s[] = "hello";

创建一个对象 - 一个大小为 6 的char数组,称为s ,使用值'h', 'e', 'l', 'l', 'o', '\\0'初始化。 该数组在内存中的分配位置以及存在的时间取决于声明出现的位置。 如果声明在一个函数内,它将一直存在到声明它的块的末尾,并且几乎肯定会在堆栈上分配; 如果它在函数之外,它可能会存储在“初始化数据段”中,该段在程序运行时从可执行文件加载到可写内存中。

另一方面,这个声明:

char *s ="hello";

创建两个对象:

  • 包含值'h', 'e', 'l', 'l', 'o', '\\0'的 6 个char只读数组,它没有名称并且具有静态存储持续时间(意味着它在程序的整个生命周期中都存在);
  • 一个指向字符的指针类型的变量,称为s ,它用该未命名的只读数组中第一个字符的位置进行初始化。

未命名的只读数组通常位于程序的“文本”段中,这意味着它与代码本身一起从磁盘加载到只读内存中。 s指针变量在内存中的位置取决于声明出现的位置(就像在第一个示例中一样)。

鉴于声明

char *s0 = "hello world";
char s1[] = "hello world";

假设以下假设内存映射:

0x01  0x02  0x03  0x04
        0x00008000: 'h'   'e'   'l'   'l'
        0x00008004: 'o'   ' '   'w'   'o'
        0x00008008: 'r'   'l'   'd'   0x00
        ...
s0:     0x00010000: 0x00  0x00  0x80  0x00
s1:     0x00010004: 'h'   'e'   'l'   'l'
        0x00010008: 'o'   ' '   'w'   'o'
        0x0001000C: 'r'   'l'   'd'   0x00

字符串文字"hello world"是一个 12 元素的char数组(C++ 中的const char ),具有静态存储持续时间,这意味着它的内存在程序启动时分配,并保持分配到程序终止。 尝试修改字符串文字的内容会调用未定义的行为。

线

char *s0 = "hello world";

s0定义为一个指向char并具有自动存储持续时间的指针(意味着变量s0只存在于它被声明的范围内)并将字符串文字的地址(在本例中为0x00008000 )复制到它。 请注意,由于s0指向字符串文字,因此不应将其用作任何试图修改它的函数的参数(例如, strtok()strcat()strcpy()等)。

线

char s1[] = "hello world";

s1定义为具有自动存储持续时间的 12 元素char数组(长度取自字符串文字),并将文字内容复制到数组中。 从内存映射中可以看出,我们有两个字符串"hello world"副本; 不同之处在于您可以修改s1包含的字符串。

s0s1在大多数情况下是可以互换的; 以下是例外情况:

sizeof s0 == sizeof (char*)
sizeof s1 == 12

type of &s0 == char **
type of &s1 == char (*)[12] // pointer to a 12-element array of char

您可以重新分配变量s0以指向不同的字符串文字或另一个变量。 您不能重新分配变量s1以指向不同的数组。

C99 N1256 草案

字符串文字有两种不同的用途:

  1. 初始化char[]

     char c[] = "abc";

    这是“更神奇的”,并在 6.7.8/14“初始化”中描述:

    字符类型的数组可以由字符串文字初始化,可选地括在大括号中。 字符串文字的连续字符(如果有空间或数组大小未知,则包括终止空字符)初始化数组的元素。

    所以这只是一个快捷方式:

     char c[] = {'a', 'b', 'c', '\\0'};

    像任何其他常规数组一样,可以修改c

  2. 在其他任何地方:它生成一个:

    所以当你写:

     char *c = "abc";

    这类似于:

     /* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed;

    注意从char[]char *的隐式转换,这总是合法的。

    然后如果你修改c[0] ,你也会修改__unnamed ,它是 UB 。

    这在 6.4.5“字符串文字”中有记录:

    5 在转换阶段 7 中,将一个字节或值为零的代码附加到由一个或多个字符串文字产生的每个多字节字符序列。 然后使用多字节字符序列初始化一个静态存储持续时间和长度刚好足以包含该序列的数组。 对于字符串文字,数组元素具有 char 类型,并使用多字节字符序列的各个字节进行初始化 [...]

    6 不确定这些数组是否不同,只要它们的元素具有适当的值。 如果程序尝试修改这样的数组,则行为未定义。

6.7.8/32《初始化》给出了一个直接的例子:

例 8:声明

char s[] = "abc", t[3] = "abc";

定义“普通”字符数组对象st其元素用字符串文字初始化。

此声明等同于

char s[] = { 'a', 'b', 'c', '\\0' }, t[] = { 'a', 'b', 'c' };

数组的内容是可修改的。 另一方面,声明

char *p = "abc";

定义p类型为“指向 char 的指针”,并将其初始化为指向一个类型为“char 数组”的对象,长度为 4,其元素用字符串文字初始化。 如果尝试使用p修改数组的内容,则行为未定义。

GCC 4.8 x86-64 ELF 实现

程序:

#include <stdio.h>

int main(void) {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

编译和反编译:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

输出包含:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

结论:GCC 将char*存储在.rodata部分,而不是.text

但是请注意,默认链接器脚本将.rodata.text放在同一段中,该具有执行但没有写入权限。 这可以通过以下方式观察到:

readelf -l a.out

其中包含:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

如果我们对char[]做同样的事情:

 char s[] = "abc";

我们获得:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

所以它被存储在堆栈中(相对于%rbp )。

char s[] = "hello";

s声明为一个char数组,该数组的长度足以容纳初始值设定项(5 + 1 个char s),并通过将给定字符串文字的成员复制到数组中来初始化数组。

char *s = "hello";

s声明为指向一个或多个(在本例中为多个) char s 的指针,并将其直接指向包含文字"hello"的固定(只读)位置。

char s[] = "Hello world";

这里, s是一个字符数组,如果我们愿意,可以覆盖它。

char *s = "hello";

字符串文字用于在此指针s指向的内存中的某处创建这些字符块。 我们可以在这里通过改变它来重新分配它所指向的对象,但只要它指向一个字符串文字,它所指向的字符块就不能改变。

只是补充一点:您还可以获得不同的尺寸值。

printf("sizeof s[] = %zu\n", sizeof(s));  //6
printf("sizeof *s  = %zu\n", sizeof(s));  //4 or 8

如上所述,对于数组'\\0'将被分配为最终元素。

另外,请考虑到,对于只读目的,两者的使用是相同的,您可以通过使用[]*(<var> + <index>)格式进行索引来访问字符:

printf("%c", x[1]);     //Prints r

和:

printf("%c", *(x + 1)); //Prints r

显然,如果你试图做

*(x + 1) = 'a';

当您尝试访问只读内存时,您可能会遇到分段错误。

char *str = "Hello";

上面将 str 设置为指向硬编码在程序二进制图像中的文字值“Hello”,在内存中标记为只读,这意味着此字符串文字的任何更改都是非法的,并且会引发分段错误。

char str[] = "Hello";

将字符串复制到堆栈上新分配的内存中。 因此,对其进行任何更改都是允许且合法的。

means str[0] = 'M';

将 str 更改为“Mello”。

有关更多详细信息,请查看类似问题:

为什么在写入用“char *s”而不是“char s[]”初始化的字符串时会出现分段错误?

char *s1 = "Hello world"; // Points to fixed character string which is not allowed to modify
char s2[] = "Hello world"; // As good as fixed array of characters in string so allowed to modify

// s1[0] = 'J'; // Illegal
s2[0] = 'J'; // Legal

差异示例:

printf("hello" + 2); //llo
char a[] = "hello" + 2; //error

在第一种情况下,指针算术起作用(传递给函数的数组衰减为指针)。

如果是:

char *x = "fred";

x 是一个左值——它可以被赋值。 但在以下情况下:

char x[] = "fred";

x 不是左值,它是一个右值——你不能给它赋值。

根据这里的评论,很明显: char * s = "hello" ; 是个坏主意,应该在非常狭窄的范围内使用。

这可能是一个很好的机会来指出“const 正确性”是一件“好事”。 无论何时何地,您都可以使用“const”关键字来保护您的代码免受“放松”的调用者或程序员的影响,当指针发挥作用时,这些调用者或程序员通常是最“放松的”。

足够的情节剧,这就是用“const”装饰指针时可以实现的目标。 (注意:必须从右到左阅读指针声明。)以下是使用指针时保护自己的 3 种不同方法:

const DBJ* p means "p points to a DBJ that is const" 

——也就是说,DBJ 对象不能通过 p 改变。

DBJ* const p means "p is a const pointer to a DBJ" 

——也就是说,您可以通过 p 更改 DBJ 对象,但您不能更改指针 p 本身。

const DBJ* const p means "p is a const pointer to a const DBJ" 

——也就是说,你不能改变指针p本身,也不能通过p改变DBJ对象。

与尝试的 const-ant 突变相关的错误在编译时被捕获。 const 没有运行时空间或速度损失。

(当然,假设您使用的是 C++ 编译器?)

--DBJ

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM