C:char指针和数组之间的区别[重复]

Posted

技术标签:

【中文标题】C:char指针和数组之间的区别[重复]【英文标题】:C: differences between char pointer and array [duplicate] 【发布时间】: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 - 在此期间。 见***.com/a/10186799/632951 将其作为重复项关闭,因为我们有两个关于这个问题的“规范”常见问题解答。 【参考方案1】:

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

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

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

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:不,因为数组版本实际上只是数组实例化的快捷方式。所以数组在栈中分配,然后加载字符串的数据。【参考方案3】:

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

第一种是简写形式

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 吗,我想我可以,因为它们默认提升为整数,但这个答案另有说明,我很困惑,如果您能消除困惑,那将是一个很大的帮助***.com/a/41349954/5473170【参考方案4】:

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


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

【讨论】:

@Norman,这本书肯定有免费版吗?【参考方案5】:

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

【讨论】:

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

除了在两个不同的地方分配字符串“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

【讨论】:

【参考方案7】:

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

#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】:

第一种形式 (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指针和数组的区别

C99 N1256 草案

字符串字面量有两种不同的用法:

    初始化char[]:

    char c[] = "abc";      
    

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

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

    所以这只是一个捷径:

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

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

    在其他任何地方:它会生成:

    未命名 字符数组What is the type of string literals in C and C++? 带静态存储 修改后会给出 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 “字符串文字”中有记录:

    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

【讨论】:

【参考方案10】:

第二个在 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++)怎么办? 可能遇到段错误。这是未定义的。【参考方案11】:

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

【讨论】:

【参考方案12】:

对于这一行: 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;

【讨论】:

【参考方案13】:

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

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

【讨论】:

【参考方案14】:

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

【讨论】:

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

以上是关于C:char指针和数组之间的区别[重复]的主要内容,如果未能解决你的问题,请参考以下文章

C中char []和char *之间的区别[重复]

指向指针数组和指针数组的指针之间的区别[重复]

在c ++函数中返回地址和返回指针之间的区别[重复]

数组是指针? [重复]

C字符串指针与数组[重复]

C++指针和&Pointer之间的区别[重复]