我试图理解C语言中的指针,但目前对以下内容感到困惑:



char *p = "hello"


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


char p[] = "hello"


这是一个存储问候的数组。


什么是我将这两个变量都传递给此函数的区别是什么?

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


评论

这将是无效的:char p [3] =“ hello”;初始化程序字符串对于您声明的数组大小而言太长。错别字?

或者只是char p [] =“ hello”;就足够了!

C的可能重复项:char指针和数组之间的区别

C中char s []和char * s有什么区别?诚然,这也专门询问了函数参数,但这不是char特定的。

您需要了解它们的根本不同。唯一的共同点是arry p []的基是一个const指针,该指针能够通过指针访问数组p []。 p []本身保存一个字符串的内存,而* p只是指向一个字符的第一个元素的地址(即,指向已分配的字符串的基数)。为了更好地说明这一点,请考虑以下内容:char * cPtr = {'h','e','l','l','o','\ 0'}; ==>这是一个错误,因为cPtr是仅指向字符char cBuff [] = {'h','e','l','l','o','\ 0'}的指针; ==>没关系,bcos cBuff本身是一个char数组

#1 楼

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

您的示例函数printSomething需要一个指针,因此,如果您尝试将数组传递给它,如下所示:

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


编译器假装您是这样写的:

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


评论


从2012年到现在有所改变。对于字符数组,“ s”打印整个数组。即,“ hello”

– Bhanu Tez
19年5月9日在6:48

@BhanuTez不,如何存储数据以及如何处理数据是单独的问题。此示例将打印整个字符串,因为printf就是这样处理%s格式字符串的方法:从提供的地址开始,一直到遇到空终止符为止。例如,如果只想打印一个字符,则可以使用%c格式字符串。

–雅各布
19年6月17日在10:59

只是想问一下char * p =“ abc”;是否像在char []数组的情况下自动添加NULL字符\ 0一样?

–ajaysinghnegi
19年7月8日在17:53

为什么我可以设置char * name; name =“ 123”;但是可以对int类型做同样的事情吗?在使用%c打印name之后,输出是不可读的字符串:

– TomSawyer
4月23日19:52

#2 楼

我们来看一下:

#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 []是不同的类型,它们由编译器进行不同的处理(pointer =地址+指针类型的表示形式,array =指针+数组的可选长度(如果知道,例如,如果数组是静态分配的),则可以在标准中找到详细信息。而且在运行时级别上它们之间没有区别(在汇编器中,差不多,请参阅下文)。

此外,C FAQ中还有一个相关问题:


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

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


如果我尝试给p [i]赋新值,则程序崩溃。 />
A:字符串文字(C源代码中双引号字符串的正式术语)可以以两种略有不同的方式使用:


一个char数组,如char a []的声明中所示,它指定该数组中字符的初始值(并在必要时指定其大小)。
在其他任何地方,它都变成一个未命名的静态字符数组,并且该未命名数组可以存储在只读存储器中,因此不必修改。在表达式上下文中,通常将数组立即转换为指针(请参见第6节),因此第二个声明将p初始化为指向未命名数组的第一个元素。

一些编译器有一个开关来控制字符串文字是否可写(用于编译旧代码),并且某些编译器可以选择将字符串文字形式化为const char数组(以获取更好的错误)

另请参阅问题1.31、6.1、6.2、6.8和11.8b。

参考文献:K&R2 Sec。 5.5页104

ISO Sec。 6.1.4,第二节6.5.7

基本原理部分。 3.1.4

H&S秒2.7.4第31-2页


评论


在sizeof(q)中,为什么q不会像@Jon在回答中提到的那样变成指针?

–加里普
16年4月21日在19:23

@garyp q不会衰减为指针,因为sizeof是一个运算符,而不是一个函数(即使sizeof是一个函数,仅当该函数期望使用char指针时,q才会衰减)。

– GiriB
16年8月14日在17:07

谢谢,但是用printf(“%u \ n”代替printf(“%zu \ n”),我认为您应该删除z。

–扎卡里亚
18年2月17日在20:11

#3 楼


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


C99 N1256草案

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



初始化char[]

char c[] = "abc";      


这是“更多的魔术”,在6.7.8 /中有描述。 14“ Initialization”:


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


因此,这只是以下操作的快捷方式:

char c[] = {'a', 'b', 'c', '
char *c = "abc";
'};


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


其他任何地方:它会生成一个:


未命名的char数组。C和C ++中的字符串文字类型是什么?

带静态存储
,如果被修改,将给出UB(未定义的行为)

因此,当您编写时:

/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;


这类似于:

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


请注意从char[]char *的隐式强制转换,这始终是合法的。

然后,如果您修改c[0],您还将修改__unnamed,是UB。

在6.4.5“字符串文字”中进行了记录:


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

6未指定这些数组是唯一的,只要它们的元素具有
适当的值。如果程序尝试修改此类数组,则行为为
未定义。



6.7.8 / 32“初始化”给出了一个直接的示例:
char s[] = { 'a', 'b', 'c', '
char *p = "abc";
' }, t[] = { 'a', 'b', 'c' };


定义了“普通”字符数组对象st,其元素使用字符串文字初始化。

此声明与

#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


定义类型为“ pointer to char”的p并将其初始化为指向长度为“ char数组”类型的对象4,其元素用字符串文字初始化。如果尝试使用p修改数组的内容,则该行为未定义。

GCC 4.8 x86-64 ELF实现

程序:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   
 char s[] = "abc";
x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata


编译和反编译:

17:   c7 45 f0 61 62 63 00    movl   
readelf -l a.out
x636261,-0x10(%rbp)


输出包含:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata


结论:GCC将char*存储在.rodata部分中,而不是存储在.text中。

如果对char[]执行相同操作:

q4312078q

我们获得:

q4312078q

,因此它被存储在堆栈中(相对于%rbp)。

但是请注意,默认的链接描述文件将.rodata.text放在同一段中,该段具有执行但没有写许可权。这可以通过以下方式观察到:

q4312078q

其中包含:

q4312078q

评论


@ leszek.hanusz未定义行为stackoverflow.com/questions/2766731/…Google“ C语言UB” ;-)

– Ciro Santilli郝海东冠状病六四事件法轮功
16年5月3日,9:47

#4 楼

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

#5 楼

对于这样的情况,效果是相同的:您最终将第一个字符的地址传递到字符串中。

声明显然并不相同。

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

char *p = "hello";


以下为字符串预留了内存。因此实际上可以使用更少的内存。

char p[10] = "hello";


评论


codeplusplus.blogspot.com/2007/09/…“但是,初始化变量会给阵列带来巨大的性能和空间损失”

–叶
13年10月10日在14:22

@leef:我认为这取决于变量的位置。如果它在静态内存中,我认为可以将数组和数据存储在EXE映像中,而根本不需要任何初始化。否则,是的,如果必须先分配数据然后再复制静态数据,肯定会变慢。

–乔纳森·伍德
2015年3月31日15:34

#6 楼

在APUE中,第5.14节:

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



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


引用的文本与@Ciro Santilli的解释匹配。

#7 楼

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

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


是一个真实的语句

评论


我将数组描述为指向内存块地址的指针。因此,为什么*(arr + 1)带您到arr的第二个成员。如果*(arr)指向32位内存地址,例如bfbcdf5e,然后*(arr + 1)指向bfbcdf60(第二个字节)。因此,如果操作系统不存在段错误,为什么超出数组范围将导致奇怪的结果。如果int a = 24;是在地址bfbcdf62处,那么假设没有首先发生段错误,则访问arr [2]可能会返回24。

– Braden Best
2014年2月5日在3:05



#8 楼

char p[3] = "hello"吗?应该是char p[6] = "hello",请记住,C中“字符串”的末尾有一个'\ 0'字符。无论如何,C中的数组只是指向调整对象中第一个对象的指针记忆。唯一不同的是语义上的。虽然您可以更改指针的值以指向内存中的其他位置,但是数组在创建后将始终指向相同的位置。
使用数组时,“ new”和“ delete”也会自动为您做了。