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;
您会看到,虽然&amessage
等于&amessage[0]
,但&pmessage
和&pmessage[0]
并非如此。实际上,您会看到存储在amessage
中的字符串存在于堆栈中,而pmessage
指向的字符串存在于其他位置。
最后一个 printf 显示字符串文字的地址。如果您的编译器执行“字符串池”,那么将只有一个字符串“now is the time”的副本——您会看到它的地址与amessage
的地址不同。这是因为amessage
在初始化时获得了字符串的副本。
最后,重点是amessage
将字符串存储在它自己的内存中(在本例中是在堆栈上),而pmessage
指向存储在别处的字符串。
【讨论】:
错了。该数组包含字符串文字的副本 - 它不是同一个数组。 也许我有点模棱两可。让我澄清一下:有一个名为 message 的变量。有一个字符串,其内容是“现在是时间”。消息的地址与该字符串中“n”的地址相同。这就是我所说的关系。当然,在程序的地址空间中可能还有其他“现在是时候”的副本,但我说的是存储在数组中的副本。 现在对我来说很有意义。感谢您的进一步解释! @DanMoulding 我已编辑以将编辑与原始文本结合起来。就目前而言,未经编辑的开头段落具有误导性。希望这没问题! @M.M&amessage
和&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";
定义“普通”字符数组对象
s
和t
,其元素使用字符串字面量进行初始化。此声明与
相同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指针和数组之间的区别[重复]的主要内容,如果未能解决你的问题,请参考以下文章