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

时间:2009-11-09 22:34:22

标签: c string char constants

在C语言中,可以在声明中使用字符串文字:

char s[] = "hello";

或者像这样:

char *s = "hello";

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

14 个答案:

答案 0 :(得分:522)

这里的区别在于

char *s = "Hello world";

会将"Hello world"放在内存的只读部分中,并使s成为指针,使得此内存上的任何写入操作都是非法的。

在做的时候:

char s[] = "Hello world";

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

s[0] = 'J';

合法的。

答案 1 :(得分:141)

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

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()或类似的方式复制。如果数组在全局范围内分配,当然没问题。

答案 2 :(得分:66)

此声明:

char s[] = "hello";

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

另一方面,这个声明:

char *s ="hello";

创建两个对象:

  • 包含值char的6个'h', 'e', 'l', 'l', 'o', '\0'只读数组,其中没有名称且静态存储持续时间(含义)它在整个生命周期中存在);和
  • 一个类型为pointer-to-char的变量,名为s,它使用该未命名的只读数组中第一个字符的位置进行初始化。

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

答案 3 :(得分:57)

鉴于声明

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仅存在于声明它的范围内)并复制地址< / em>字符串文字(在此示例中为0x00008000)。请注意,由于s0指向字符串文字,因此不应将其用作尝试修改它的任何函数的参数(例如strtok()strcat(),{{1}等等)。

该行

strcpy()

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

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

s1

您可以重新分配变量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重新指定为指向其他数组。

答案 4 :(得分:30)

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如果这些数组的元素具有,则未指定这些数组是否是不同的   适当的价值观如果程序试图修改这样的数组,则行为是   未定义。

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

      

    示例8:声明

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

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

         

    此声明与

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

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

    char *p = "abc";
    
         

    使用类型“指向char的指针”定义p并将其初始化为指向类型为“array of 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

    如果我们对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
    

答案 5 :(得分:15)

char s[] = "hello";

s声明为char的数组,该数组足以保存初始值设定项(5 + 1 char s)并通过复制给定字符串的成员来初始化数组字面意思到数组。

char *s = "hello";

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

答案 6 :(得分:4)

char s[] = "Hello world";

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

char *s = "hello";

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

答案 7 :(得分:3)

添加:您还可以获得不同的尺寸值。

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

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

答案 8 :(得分:3)

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

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

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

显然,如果你试图做

*(x + 1) = 'a';

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

答案 9 :(得分:2)

char *str = "Hello";

以上设置指向字面值&#34; Hello&#34;在程序的二进制映像中进行了硬编码,在内存中标记为只读,意味着此字符串文字的任何更改都是非法的,这将导致分段错误。

char str[] = "Hello";

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

means str[0] = 'M';

会将str更改为&#34; Mello&#34;。

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

Why do I get a segmentation fault when writing to a string initialized with "char *s" but not "char s[]"?

答案 10 :(得分:0)

以下情况:

char *x = "fred";

x是lvalue - 可以分配给它。但在以下情况下:

char x[] = "fred";

x不是左值,它是右值 - 你不能分配它。

答案 11 :(得分:0)

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

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

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

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

- 也就是说,无法通过p。

更改DBJ对象
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

答案 12 :(得分:0)

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

答案 13 :(得分:0)

差异示例:

  openDialog2(maintenance): void {
    this.selectedMaintenance = maintenance;
    this.selectMaintenancesForMachine(this.selectedMaintenance.idmachine);
    this.selectComponentsForMachine(this.selectedMaintenance.idmachine);
    //Assegno le variabili
    this.idmachine = this.selectedMaintenance.idmachine;
    this.machinenumber = this.selectedMaintenance.machinenumber;
    this.dialogRef2 = this.dialogService.open(this.dialog2);
  }

在第一种情况下,指针算术有效(传递给函数的数组会衰减到指针)。