C中的char数组和char指针有什么区别?


216

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

  • char *p = "hello"

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

  • char p[] = "hello"

    这是一个存储hello的数组。

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

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

5
这将无效:char p[3] = "hello";初始化程序字符串对于您声明的数组大小而言过长。错别字?
科迪·格雷

16
或者char p[]="hello";就足够了!
2014年


1
C中char s []和char * s什么区别?没错,这也专门询问了功能参数,但这不是char特定的。
西罗Santilli郝海东冠状病六四事件法轮功

1
您需要了解它们的根本不同。唯一的共同点是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本身是一个字符数组
Ilavarasan

Answers:


222

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

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

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

编译器假装您编写了以下代码:

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

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

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

只是想问一下是否char *p = "abc";\0字符[]数组的情况下自动添加NULL字符?
毕马威(KPMG)

为什么我可以设置char *name; name="123";但可以对int类型进行相同设置?并使用后%c进行打印name,输出是不可读的字符串:
TomSawyer

83

让我们来看看:

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

此外,还有一个相关的问题Ç常见问题

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

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

如果尝试为p [i]分配新值,程序将崩溃。

:字符串文字(C源代码中双引号字符串的正式术语)可以以两种略有不同的方式使用:

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

一些编译器具有控制字符串文字是否可写的开关(用于编译旧代码),而某些编译器可能具有将字符串文字形式上视为const char数组的选项(以更好地捕获错误)。

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

参考:K&R2第2节。5.5页 104

ISO秒 6.1.4,第二节 6.5.7

基本原理 3.1.4

H&S秒 2.7.4第31-2页


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

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

谢谢,但是printf(“%u \ n”而不是printf(“%zu \ n”,我认为您应该删除
z。– Zakaria

33

C中的char数组与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],那么您还修改了__unnamedUB。

    在6.4.5“字符串文字”中有记录:

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

    6如果这些数组的元素具有适当的值,则不确定这些数组是否不同。如果程序尝试修改这样的数组,则行为是不确定的。

6.7.8 / 32“初始化”给出了一个直接的示例:

例8:声明

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

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

此声明与

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

数组的内容是可修改的。另一方面,声明

char *p = "abc";

定义p类型为“ 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

2
@ leszek.hanusz未定义行为stackoverflow.com/questions/2766731/...谷歌“C语言UB” ;-)
西罗桑蒂利郝海东冠状病六四事件法轮功

9

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


6

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

声明显然并不相同。

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

char *p = "hello";

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

char p[10] = "hello";

codeplusplus.blogspot.com/2007/09/… “但是,初始化变量
会给

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

3

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

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

是一个真实的陈述


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

3

APUE,第5.14节:

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

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

引用的文字与@Ciro Santilli的解释相符。


1

char p[3] = "hello"?应该char p[6] = "hello"记住,C的“字符串”末尾有一个“ \ 0”字符。

无论如何,C语言中的数组只是指向内存中调整对象的第一个对象的指针。唯一不同的是语义上的。虽然您可以更改指针的值以指向内存中的其他位置,但数组创建后始终将指向相同的位置。
在使用数组时,也会自动为您完成“新建”和“删除”操作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.