C中的char数组与char指针有什么区别?

时间:2012-04-17 07:12:44

标签: c arrays pointers

我正在尝试理解C中的指针,但我目前对以下内容感到困惑:

  • char *p = "hello"
    

    这是一个指向字符数组的字符指针,从 h 开始。

  • char p[] = "hello"
    

    这是一个存储 hello 的数组。

将这两个变量传递给此函数有什么区别?

void printSomething(char *p)
{
    printf("p: %s",p);
}

8 个答案:

答案 0 :(得分:201)

char*char[] 是不同的类型,但并非在所有情况下都能立即显现出来。这是因为数组衰减为指针,这意味着如果提供类型为char[]的类型为char*的表达式,则编译器会自动将数组转换为指针到它的第一个元素。

你的示例函数printSomething需要一个指针,所以如果你试图像这样传递一个数组:

char s[10] = "hello";
printSomething(s);

编译器假装你写了这个:

char s[10] = "hello";
printSomething(&s[0]);

答案 1 :(得分:74)

让我们看看:

#include <stdio.h>
#include <string.h>

int main()
{
    char *p = "hello";
    char q[] = "hello"; // no need to count this

    printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64
    printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both

    // size_t strlen(const char *s) and we don't get any warnings here:
    printf("%zu\n", strlen(p)); // => 5
    printf("%zu\n", strlen(q)); // => 5

    return 0;
}

foo *和foo []是不同的类型,它们由编译器处理不同(指针=地址+指针类型的表示,数组=指针+数组的可选长度,如果已知,例如,如果数组静态分配),详细信息可以在标准中找到。并且在运行时级别它们之间没有区别(在汇编程序中,好吧,差不多,见下文)。

此外,question中还有一个相关的C FAQ

  

:这些初始化之间有什么区别?

char a[] = "string literal";   
char *p  = "string literal";   
     

如果我尝试为p [i]指定新值,我的程序会崩溃。

     

A :字符串文字(C源中双引号字符串的正式术语)可以两种略有不同的方式使用:

     
      
  1. 作为char数组的初始值设定项,如char a []的声明,它指定该数组中字符的初始值(如果需要,还指定其大小)。
  2.   
  3. 在其他地方,它变成一个未命名的静态字符数组,这个未命名的数组可能存储在只读存储器中,因此不一定能被修改。在表达式上下文中,像往常一样将数组一次转换为指针(参见第6节),因此第二个声明将p初始化为指向未命名数组的第一个元素。
  4.         

    有些编译器有一个开关控制字符串文字是否可写(用于编译旧代码),有些编译器可能有选项可以将字符串文字正式地视为const char数组(以便更好地捕获错误)。

         

    另见问题1.31,6.1,6.2,6.8和11.8b。

         

    参考文献:K&amp; R2 Sec。 5.5 p。 104

         

    ISO Sec。 6.1.4,Sec。 6.5.7

         

    理由二。 3.1.4

         

    H&amp; S Sec。 2.7.4 pp.31-2

答案 2 :(得分:30)

  

C中的char数组与char指针有什么区别?

C99 N1256草案

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

  1. 初始化char[]

    char c[] = "abc";      
    

    这是更多魔术&#34;,并描述于6.7.8 / 14&#34;初始化&#34;:

      

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

    所以这只是一个捷径:

    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&#34;字符串文字&#34;:

    中有记录
      

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

         

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

  3. 6.7.8 / 32&#34;初始化&#34;给出了一个直接的例子:

      

    示例8:声明

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

    定义&#34; plain&#34; char数组对象st,其元素用字符串文字初始化。

         

    此声明与

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

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

    char *p = "abc";
    
         

    定义p类型&#34;指向char&#34;并初始化它以指向一个类型为#34的对象; char&#34;长度为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
    

答案 3 :(得分:9)

您不能更改字符串常量的内容,这是第一个p指向的内容。第二个p是一个用字符串常量初始化的数组,你可以更改其内容。

答案 4 :(得分:6)

对于这样的情况,效果是一样的:你最终会在一串字符中传递第一个字符的地址。

声明显然不一样。

下面为字符串和字符指针留出内存,然后将指针初始化为指向字符串中的第一个字符。

char *p = "hello";

虽然以下内容仅为字符串留出内存。所以它实际上可以使用更少的内存。

char p[10] = "hello";

答案 5 :(得分:2)

据我所知,数组实际上是一组指针。 例如

p[1]== *(&p+1)

是一个真实的陈述

答案 6 :(得分:1)

char p[3] = "hello"?应该char p[6] = "hello"记住C中“字符串”末尾有一个'\ 0'字符。

无论如何,C中的数组只是指向内存中调整对象的第一个对象的指针。唯一不同的是语义。虽然您可以将指针的值更改为指向内存中的其他位置,但数组在创建后始终指向同一位置。
同样在使用数组时,会自动为您执行“新建”和“删除”。

答案 7 :(得分:1)

来自 APUE ,第5.14节:

char    good_template[] = "/tmp/dirXXXXXX"; /* right way */
char    *bad_template = "/tmp/dirXXXXXX";   /* wrong way*/
  

...对于第一个模板,名称是在堆栈上分配的,因为我们使用了   数组变量。但是,对于第二个名称,我们使用指针。在这种情况下,只有   指针本身的内存位于堆栈中;编译器将字符串安排为   被存储在可执行文件的只读段中。当mkstemp函数尝试时   修改字符串时,会发生分段错误。

引用的文字与@Ciro Santilli的解释相符。