C:char指针和数组之间的差异

时间:2009-08-26 16:00:27

标签: c arrays pointers

考虑:

char amessage[] = "now is the time";
char *pmessage = "now is the time";

我从The C Programming Language第2版中读到上述两个陈述没有做同样的事情。

我一直认为数组是一种操作指针来存储一些数据的便捷方法,但显然情况并非如此...... C中数组和指针之间的“非平凡”差异是什么?

14 个答案:

答案 0 :(得分:142)

这是一个假设的记忆图,显示了两个声明的结果:

                0x00  0x01  0x02  0x03  0x04  0x05  0x06  0x07
    0x00008000:  'n'   'o'   'w'   ' '   'i'   's'   ' '   't'
    0x00008008:  'h'   'e'   ' '   't'   'i'   'm'   'e'  '\0'
        ...
amessage:
    0x00500000:  'n'   'o'   'w'   ' '   'i'   's'   ' '   't'
    0x00500008:  'h'   'e'   ' '   't'   'i'   'm'   'e'  '\0'
pmessage:
    0x00500010:  0x00  0x00  0x80  0x00

字符串文字“now is the time”存储为内存地址0x00008000的16元素char数组。该存储器可能不可写;最好假设它不是。您永远不应尝试修改字符串文字的内容。

声明

char amessage[] = "now is the time";

在内存地址0x00500000处分配一个16元素的char数组,并将字符串文字的内容复制到它。这个记忆是可写的;你可以将amessage的内容改为你心中的内容:

strcpy(amessage, "the time is now");

声明

char *pmessage = "now is the time";

在内存地址0x00500010处为char分配一个指针,并将字符串文字的地址复制到它。

由于pmessage指向字符串文字,因此不应将其用作需要修改字符串内容的函数的参数:

strcpy(amessage, pmessage); /* OKAY */
strcpy(pmessage, amessage); /* NOT OKAY */
strtok(amessage, " ");      /* OKAY */
strtok(pmessage, " ");      /* NOT OKAY */
scanf("%15s", amessage);      /* OKAY */
scanf("%15s", pmessage);      /* NOT OKAY */

等等。如果你改变了pmessage指向amessage:

pmessage = amessage;

然后它可以在任何地方使用消息可以使用。

答案 1 :(得分:95)

是的,但这是一个微妙的区别。基本上,前者:

char amessage[] = "now is the time";

定义一个数组,其成员位于当前作用域的堆栈空间中,而:

char *pmessage = "now is the time";

定义一个指针,该指针位于当前作用域的堆栈空间中,但在其他位置引用内存(在此处,“现在是时间”存储在内存中的其他位置,通常是字符串表)。

另请注意,由于属于第二个定义的数据(显式指针)未存储在当前作用域的堆栈空间中,因此未指定它将存储的确切位置,并且不应进行修改。

编辑:正如Mark,GMan和Pavel所指出的,当在这些变量中的任何一个上使用address-of运算符时,也存在差异。例如,& pmessage返回char **类型的指针,或指向chars指针的指针,而& amessage返回char(*)[16]类型的指针,或指向16个字符数组的指针(就像一个char **,需要被解除引用两次作为点亮点)。

答案 2 :(得分:12)

数组包含元素。指针指向它们。

第一种是简短的说法

char amessage[16];
amessage[0] = 'n';
amessage[1] = 'o';
...
amessage[15] = '\0';

也就是说,它是一个包含所有字符的数组。特殊初始化为您初始化它,并自动确定它的大小。数组元素是可修改的 - 您可以覆盖其中的字符。

第二种形式是指针,只指向字符。它不直接存储字符。由于数组是字符串文字,因此无法获取指针并写入指向的位置

char *pmessage = "now is the time";
*pmessage = 'p'; /* undefined behavior! */

这个代码可能会在你的盒子上崩溃。但它可能会做任何它喜欢的事情,因为它的行为是不确定的。

答案 3 :(得分:6)

我不能有用地添加其他答案,但我会在Deep C Secrets中注意到,Peter van der Linden详细介绍了这个例子。如果你问这些问题,我想你会喜欢这本书。


P.S。您可以为pmessage分配新值。您无法为amessage分配新值;它是不可变的

答案 4 :(得分:5)

如果定义了一个数组,使其大小在声明时可用,sizeof(p)/sizeof(type-of-array)将返回数组中元素的数量。

答案 5 :(得分:4)

除了在两个不同的地方分配字符串“现在是时间”的内存外,还应该记住,数组名称充当指针而不是指针pmessage是变量。主要区别在于指针变量可以修改为指向其他位置而数组不能。

char arr[] = "now is the time";
char *pchar = "later is the time";

char arr2[] = "Another String";

pchar = arr2; //Ok, pchar now points at "Another String"

arr = arr2; //Compiler Error! The array name can be used as a pointer VALUE
            //not a pointer VARIABLE

答案 6 :(得分:4)

第一个表单(amessage)定义一个变量(数组),其中包含字符串"now is the time"的副本。

第二种形式(pmessage)定义一个变量(一个指针),它位于与字符串"now is the time"的任何副本不同的位置。

试试这个程序:

#include <inttypes.h>
#include <stdio.h>

int main (int argc, char *argv [])
{
     char  amessage [] = "now is the time";
     char *pmessage    = "now is the time";

     printf("&amessage   : %#016"PRIxPTR"\n", (uintptr_t)&amessage);
     printf("&amessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&amessage[0]);
     printf("&pmessage   : %#016"PRIxPTR"\n", (uintptr_t)&pmessage);
     printf("&pmessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&pmessage[0]);

     printf("&\"now is the time\": %#016"PRIxPTR"\n",
            (uintptr_t)&"now is the time");

     return 0;
}

您会看到虽然&amessage等于&amessage[0],但&pmessage&pmessage[0]却不是这样。实际上,您会看到存储在amessage中的字符串存在于堆栈中,而pmessage指向的字符串位于其他位置。

最后一个printf显示字符串文字的地址。如果您的编译器执行“字符串池化”,则字符串只有一个副本“现在是时间” - 您将看到其地址与amessage的地址不同。这是因为amessage在初始化时会获得字符串的副本

最后,重点是amessage将字符串存储在自己的内存中(在此示例中为堆栈),而pmessage指向存储在其他位置的字符串。

答案 7 :(得分:4)

指针只是一个保存内存地址的变量。请注意,您正在玩​​“字符串文字”,这是另一个问题。内联解释的差异:基本上:

#include <stdio.h>

int main ()
{

char amessage[] = "now is the time"; /* Attention you have created a "string literal" */

char *pmessage = "now is the time";  /* You are REUSING the string literal */


/* About arrays and pointers */

pmessage = NULL; /* All right */
amessage = NULL; /* Compilation ERROR!! */

printf ("%d\n", sizeof (amessage)); /* Size of the string literal*/
printf ("%d\n", sizeof (pmessage)); /* Size of pmessage is platform dependent - size of memory bus (1,2,4,8 bytes)*/

printf ("%p, %p\n", pmessage, &pmessage);  /* These values are different !! */
printf ("%p, %p\n", amessage, &amessage);  /* These values are THE SAME!!. There is no sense in retrieving "&amessage" */


/* About string literals */

if (pmessage == amessage)
{
   printf ("A string literal is defined only once. You are sharing space");

   /* Demostration */
   "now is the time"[0] = 'W';
   printf ("You have modified both!! %s == %s \n", amessage, pmessage);
}


/* Hope it was useful*/
return 0;
}

答案 8 :(得分:3)

第二个在ELF的某个只读部分中分配字符串。 请尝试以下方法:

#include <stdio.h>

int main(char argc, char** argv) {
    char amessage[] = "now is the time";
    char *pmessage = "now is the time";

    amessage[3] = 'S';
    printf("%s\n",amessage);

    pmessage[3] = 'S';
    printf("%s\n",pmessage);
}

你将在第二个任务中得到一个段错误(pmessage [3] ='S')。

答案 9 :(得分:3)

  

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
    

答案 10 :(得分:1)

以上答案肯定已经回答了你的问题。但我想建议你阅读丹尼斯·里奇爵士撰写的The Development of C Language中的“胚胎C”一段。

答案 11 :(得分:0)

对于这一行:     char amessage [] =“现在是时间”;

编译器将评估amessage的使用作为指向包含字符“now is the time”的数组开头的指针。编译器为“now is the time”分配内存,并使用字符串“now is the time”对其进行初始化。您知道该消息的存储位置,因为消息始终指的是该消息的开头。 amessage可能没有给出新值 - 它不是变量,它是字符串的名称“现在是时间”。

这一行:     char * pmessage =“现在是时间”;

声明一个变量,pmessage是初始化(给定一个初始值)字符串“now is the time”的起始地址。与消息不同,pmessage可以赋予新的价值。在这种情况下,与前一种情况一样,编译器还在存储器的其他地方存储“现在是时间”。 例如,这将导致pmessage指向“i”开始“是时间”。     pmessage = pmessage + 4;

答案 12 :(得分:-1)

这是我对数组和指针之间关键差异的总结,这是我为自己做的:

//ATTENTION:
    //Pointer depth 1
     int    marr[]  =  {1,13,25,37,45,56};      // array is implemented as a Pointer TO THE FIRST ARRAY ELEMENT
     int*   pmarr   =  marr;                    // don't use & for assignment, because same pointer depth. Assigning Pointer = Pointer makes them equal. So pmarr points to the first ArrayElement.

     int*   point   = (marr + 1);               // ATTENTION: moves the array-pointer in memory, but by sizeof(TYPE) and not by 1 byte. The steps are equal to the type of the array-elements (here sizeof(int))

    //Pointer depth 2
     int**  ppmarr  = &pmarr;                   // use & because going one level deeper. So use the address of the pointer.

//TYPES
    //array and pointer are different, which can be seen by checking their types
    std::cout << "type of  marr is: "       << typeid(marr).name()          << std::endl;   // int*         so marr  gives a pointer to the first array element
    std::cout << "type of &marr is: "       << typeid(&marr).name()         << std::endl;   // int (*)[6]   so &marr gives a pointer to the whole array

    std::cout << "type of  pmarr is: "      << typeid(pmarr).name()         << std::endl;   // int*         so pmarr  gives a pointer to the first array element
    std::cout << "type of &pmarr is: "      << typeid(&pmarr).name()        << std::endl;   // int**        so &pmarr gives a pointer to to pointer to the first array elelemt. Because & gets us one level deeper.

答案 13 :(得分:-2)

数组是一个const指针。您无法更新其值并使其指向其他任何位置。 虽然你可以做一个指针。