【问题标题】:C: differences between char pointer and array [duplicate]C:char指针和数组之间的区别[重复]
【发布时间】:2010-11-23 01:48:36
【问题描述】:

考虑:

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

我从The C Programming Language, 2nd Edition 中读到,上述两个语句的作用不同。

我一直认为数组是一种方便的方式来操作指针来存储一些数据,但显然不是这样......数组和 C 中的指针之间“不平凡”的区别是什么?

【问题讨论】:

  • 我可能记错了,但我想指出,您可以在指针上使用 [] 表示法,在数组上使用 * 表示法。从代码的角度来看,唯一的大区别是 message 的值不能改变,所以 amessage++ 应该失败(但我相信 *(amessage+1) 会成功。我相信内部还有其他差异,但它们几乎不重要。
  • 哦,通常(不是你提到的情况),数组自动分配内存,指针你必须分配你自己的内存。你的都应该指向作为程序加载的一部分分配的内存块。
  • 与 K&R 一起(顺便说一句,这是一本很棒的书),我建议您阅读 pw2.netcom.com/~tjensen/ptr/cpoint.htm - 在此期间。
  • 将其作为重复项关闭,因为我们有两个关于这个问题的“规范”常见问题解答。

标签: c arrays pointers


【解决方案1】:

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

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**,需要像 litb 指出的那样被取消引用两次)。

【讨论】:

  • 虽然如此,但这并不是最大的区别。例如,&amessage 和 &pmessage 之间有什么区别?
  • &pmessage 将是pmessage 的地址,位于堆栈中的某个位置。同样,&amessage 将是堆栈上数组的地址,与amessage 相同。但是,&amessage 的类型与 amessage 不同。
  • 不,它不是未定义的。区别在于&pmessage 的类型是char** - 指向char 的指针,&amessage 的类型是char(*)[16] - 指向16 个字符的数组的指针。这两种类型不兼容(特别是第二种,只是字符串中第一个字符的地址,而第一种是存储第一个字符地址的变量的地址)。
  • 奇怪,我猜 C 确实做到了。我认为 &amessage 将是无效的,因为 message 被解析为 code-constant pointer 。 . .
  • @Bill:不,因为数组版本实际上只是数组实例化的快捷方式。所以数组在栈中分配,然后加载字符串的数据。
【解决方案2】:

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

第一种是简写形式

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

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

第二种形式是一个指针,它只是指向字符。它不直接存储字符。由于数组是字符串文字,因此您不能将指针写入它指向的位置

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

此代码可能会在您的盒子上崩溃。但它可以做任何它喜欢的事情,因为它的行为是未定义的。

【讨论】:

  • 先生,我想问一件事,我可以在 printf 中将 %d 用于 char 吗,我想我可以,因为它们默认提升为整数,但这个答案另有说明,我很困惑,如果您能消除困惑,那将是一个很大的帮助stackoverflow.com/a/41349954/5473170
【解决方案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')。

【讨论】:

  • 这是一个非常以实现为中心的解释。如果它是一个不针对 ELF 的流行编译器(例如 VC++)怎么办?
  • 可能遇到段错误。这是未定义的。
【解决方案4】:

除了在两个不同的地方分配字符串“now is the time”的内存外,您还应该记住,数组名称充当指针,而不是指针变量是哪个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

【讨论】:

    【解决方案5】:

    数组是一个常量指针。您无法更新其值并使其指向其他任何地方。 而对于指针,你可以做到。

    【讨论】:

    • 数组是 not 指针,const 或其他。在许多情况下,数组标识符的类型会隐式地从“T 的 N 元素数组”转换为“指向 T 的指针”,但这不会使数组成为指针。
    • 同意..承认错误..感谢约翰的澄清。
    • @JohnBode 我也误解了将数组视为 const 指针。你能再引用一些资源来消除我的误解
    【解决方案6】:

    我无法对其他答案进行有用的补充,但我会指出,在 Deep C Secrets 中,Peter van der Linden 详细介绍了这个示例。如果你问这类问题,我想你会喜欢这本书的。


    附:您可以为pmessage 分配一个新值。您不能为amessage 分配新值;它是不可变的

    【讨论】:

    • @Norman,这本书肯定有免费版吗?
    【解决方案7】:

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

    【讨论】:

    • 所有其他答案都集中在“指向字符串文字地址与将字符串的字符复制到数组中”,这是有效的,但特定于 OP 的示例代码。所有人都没有提到这一点(sizeof() 的不同结果),在我看来,这是数组和指针之间非常重要的区别。
    【解决方案8】:

    第一种形式 (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;
    }
    

    您会看到,虽然&amp;amessage 等于&amp;amessage[0],但&amp;pmessage&amp;pmessage[0] 并非如此。实际上,您会看到存储在amessage 中的字符串存在于堆栈中,而pmessage 指向的字符串存在于其他位置。

    最后一个 printf 显示字符串文字的地址。如果您的编译器执行“字符串池”,那么将只有一个字符串“now is the time”的副本——您会看到它的地址与amessage 的地址不同。这是因为amessage 在初始化时获得了字符串的副本

    最后,重点是amessage 将字符串存储在它自己的内存中(在本例中是在堆栈上),而pmessage 指向存储在别处的字符串。

    【讨论】:

    • 错了。该数组包含字符串文字的副本 - 它不是同一个数组。
    • 也许我有点模棱两可。让我澄清一下:有一个名为 message 的变量。有一个字符串,其内容是“现在是时间”。消息的地址与该字符串中“n”的地址相同。这就是我所说的关系。当然,在程序的地址空间中可能还有其他“现在是时候”的副本,但我说的是存储在数组中的副本。
    • 现在对我来说很有意义。感谢您的进一步解释!
    • @DanMoulding 我已编辑以将编辑与原始文本结合起来。就目前而言,未经编辑的开头段落具有误导性。希望这没问题!
    • @M.M &amp;amessage&amp;amessage[0] 一样吗
    【解决方案9】:

    对于这一行: char message[] = "现在是时候";

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

    这一行: char *pmessage = "现在是时候";

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

    【讨论】:

      【解决方案10】:

      这是一个假设的内存映射,显示了两个声明的结果:

                      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”存储为一个 16 元素的 char 数组,位于内存地址 0x00008000。该内存可能不可写;最好假设它不是。您永远不应尝试修改字符串文字的内容。

      声明

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

      在内存地址 0x00500000 分配一个 16 元素的 char 数组,并将字符串文字的 contents 复制到它。这个内存是可写的;您可以将消息的内容更改为您喜欢的内容:

      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 更改为指向消息:

      pmessage = amessage;
      

      那么它可以在任何可以使用消息的地方使用。

      【讨论】:

      • @John Bode,很棒的答案:)。
      • 最后一行不太正确:如果用作 sizeof message&amp;pmessage ,行为会有所不同。
      • @zen:将pmessage 设置为指向另一个字符串对存储在0x0000800 的字符串文字的内容绝对没有影响。在程序退出之前,不会释放该字符串文字的存储空间。
      • @Zen:你的朋友说错了;像amessage 这样的数组不是指针。数组 objects 不在任何地方存储地址(这应该从我的答案中的内存映射中清楚)。相反,数组 表达式 将“衰减”为指针类型,除非它们是一元 &amp;sizeof 运算符的操作数(因此 sizeof 的行为有所不同)。
      • @Zen:有关详细信息,请参阅this answer
      【解决方案11】:

      以下是我自己总结的数组和指针的主要区别:

      //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.
      

      【讨论】:

        【解决方案12】:

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

        #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;
        }
        

        【讨论】:

        • 根据您的编译器,字符串文字的行为可能会有所不同。
        【解决方案13】:

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

        【讨论】:

          【解决方案14】:

          char指针和数组的区别

          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 类型为“pointer to char”,并将其初始化为指向长度为4 的“char 数组”类型的对象,其元素使用字符串字面量进行初始化。如果尝试使用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
          

          【讨论】:

            猜你喜欢
            • 2013-05-17
            • 2011-07-01
            • 1970-01-01
            • 2011-12-02
            • 1970-01-01
            • 2010-10-19
            • 1970-01-01
            相关资源
            最近更新 更多