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

在C中,可以这样做:

char s[] = "hello"; 

要么

 char *s = "hello"; 

所以我想知道有什么区别? 我想知道在编译时间和运行时间内存分配究竟发生了什么。

这里的区别是

 char *s = "Hello world"; 

会把"Hello world"放在内存只读部分 ,并且指向这个指针,使得对这个内存的任何写操作都是非法的。

在做:

 char s[] = "Hello world"; 

将string放入只读存储器,并将string复制到堆栈上新分配的内存中。 这样做

 s[0] = 'J'; 

法律。

首先,在函数参数中,它们是完全等价的:

 void foo(char *x); void foo(char x[]); // exactly the same in all respects (note! this only applies if the brackets are empty) 

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

 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()或者类似的方法来创build一个副本。 如果数组在全局范围内分配,当然没有问题。

这个声明:

 char s[] = "hello"; 

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

另一方面,这个声明:

 char *s ="hello"; 

创build两个对象:

  • 一个包含值为'h', 'e', 'l', 'l', 'o', '\0'只读数组,它没有名字并且具有静态存储持续时间在整个生活中都活着); 和
  • 一个名为s指向char的typesvariables,它被初始化为该未命名的只读数组中的第一个字符的位置。

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

鉴于声明

 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

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

该线

 char *s0 = "hello world"; 

s0定义为指向具有自动存储持续时间的char的指针(意思是variabless0只存在于声明的范围内),并将string文本(本例中为0x00008000 )的地址复制到它。 请注意,因为s0指向一个string字面值,所以它不应该被用作任何试图修改它的函数的参数(例如, strtok()strcat()strcpy()等等)。

该线

 char s1[] = "hello world"; 

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

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 

您可以重新分配variabless0以指向不同的string文字或另一个variables。 您不能重新指定variabless1指向不同的数组。

C99 N1256草案

数组文字有两种完全不同的用法:

  1. 初始化char[]

     char c[] = "abc"; 

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

    字符types的数组可以由string文字初始化,可选地用大括号括起来。 string文字的连续字符(包括终止空字符,如果有空间或数组未知大小)初始化数组的元素。

    所以这只是一个捷径:

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

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

  2. 其他地方:它会产生:

    • 无名
    • char数组什么是C和C ++中的string文字的types?
    • 与静态存储
    • 如果修改则给予UB

    所以当你写:

     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“string文字”中有logging

    5在翻译阶段7,将string或文字产生的每个多字节字符序列附加一个字节或值为零的代码。 然后使用多字节字符序列来初始化静态存储持续时间和长度的数组,以便足以包含该序列。 对于string文字,数组元素的types为char,并且用多字节字符序列的单个字节进行初始化[…]

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

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

例8:声明

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

定义“简单的”字符数组对象st其元素用string文字初始化。

这个声明是一样的

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

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

 char *p = "abc"; 

定义p的types为“指向char的指针”并将其初始化为指向长度为4的types为“char的数组”的对象,其元素用string文字初始化。 如果尝试使用p来修改数组的内容,则行为是不确定的。

GCC 4.8 x86-64 ELF实现

程序:

 #include <stdio.h> int main() { 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在.rodata节中存储char*而不是.text

如果我们对char[]做同样的处理:

  char s[] = "abc"; 

我们获得:

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

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

但是,请注意,默认链接描述文件将.rodata.text放在同一个段中,该段执行但没有写入权限。 这可以观察到:

 readelf -l a.out 

其中包含:

  Section to Segment mapping: Segment Sections... 02 .text .rodata 
 char s[] = "hello"; 

声明s是一个足够长的持有初始化方法的char数组(5 + 1个char ),并通过将给定string的成员复制到数组中来初始化数组。

 char *s = "hello"; 

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

作为补充,考虑到对于只读目的,两者的使用是相同的,您可以通过索引[]*(<var> + <index>)格式来访问char:

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

和:

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

显然,如果你试图做

 *(x + 1) = 'a'; 

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

只是补充一点:你也可以得到不同的尺寸值。

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

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

 char s[] = "Hello world"; 

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

 char *s = "hello"; 

string文字用于在指针所指向的内存中的某处创build这些字符块。 我们可以通过改变它来重新指定它指向的对象,但是只要它指向一个string文字,它所指向的字符块就不能被改变。

 char *str = "Hello"; 

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

 char str[] = "Hello"; 

将string复制到堆栈上新分配的内存。 因此,任何改变是允许的和合法的。

 means str[0] = 'M'; 

将改变“梅洛”。

有关更多详细信息,请通过类似的问题:

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

如果是:

 char *x = "fred"; 

x是一个左值 – 它可以被分配给。 但在以下情况下:

 char x[] = "fred"; 

x不是一个左值,它是一个右值 – 你不能指定它。

根据这里的评论应该是显而易见的:char * s =“hello”; 这是一个坏主意,应该在狭窄的范围内使用。

这可能是一个很好的机会,指出“常量正确”是一件“好事”。 无论何时何地,都可以使用“const”关键字来保护你的代码,从“放松的”调用者或程序员那里,当指针进场时通常是最“放松”的。

足够的情节剧,这是用“const”装饰指针时可以达到的效果。 (注意:必须从右向左阅读指针声明。)在使用指针时,有三种不同的方式来保护自己:

 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