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

w80xi6nr  于 11个月前  发布在  其他
关注(0)|答案(9)|浏览(94)

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

char *p = "hello"

字符串

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

char p[] = "hello"

这是一个存储 hello 的数组。
当我把这两个变量都传入这个函数时,有什么区别?

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

eqoofvh9

eqoofvh91#

char*char[] * 是不同的类型 *,但这并不是在所有情况下都能立即显现出来。这是因为数组 * 会衰减为指针 *,这意味着如果提供了一个char[]类型的表达式,而该表达式应该是char*类型的,编译器会自动将数组转换为指向其第一个元素的指针。
你的示例函数printSomething需要一个指针,所以如果你试图像这样传递一个数组给它:

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

字符串
编译器假装你写了这个:

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

mwg9r5ms

mwg9r5ms2#

让我们看看:

#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[]是不同的类型,编译器对它们的处理方式也不同(指针=地址+指针类型的表示,数组=指针+数组的可选长度,如果已知的话,例如,如果数组是静态分配的话),细节可以在标准中找到。在运行时级别上,它们之间没有区别(在汇编程序中,嗯,几乎是这样,见下文)。
另外,在C FAQ中还有一个相关的question

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

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


如果我试图给p[i]赋值,我的程序就会崩溃。

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

1.作为char数组的初始化器,就像在char a[]的声明中一样,它指定该数组中字符的初始值(如果需要,还指定其大小)。
1.在其他地方,它会变成一个未命名的静态字符数组,这个未命名的数组可能存储在只读存储器中,因此不需要修改。在表达式上下文中,数组会像往常一样立即转换为指针(参见第6节),因此第二个声明会初始化p,使其指向未命名数组的第一个元素。
有些编译器有一个开关来控制字符串文字是否可写(用于编译旧代码),而有些编译器可能有选项来使字符串文字被正式视为常量字符数组(用于更好地捕捉错误)。
另请参见问题1.31、6.1、6.2、6.8和11.8b。
参考:K&R2第5.5节第104页
ISO第6.1.4节和第6.5.7节
依据第3.1.4节
健康与安全第2.7.4节,第31-2页

7ivaypg9

7ivaypg93#

在C语言中,字符数组和字符指针有什么区别?

C99 N1256草案

字符串文字有两种不同的用法:
1.初始化char[]

char c[] = "abc";

字符串
这是“更神奇的”,并在6. 7. 8/14“初始化”中进行了描述:
字符类型的数组可以由字符串文字初始化,也可以用大括号括起来。字符串文字的连续字符(如果有空间或数组大小未知,则包括终止的空字符)初始化数组的元素。
所以这只是一个捷径:

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


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

所以当你写:

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";


定义了“纯”char数组对象st,它们的元素是用字符串文字初始化的。
此声明与

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


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

char *p = "abc";


定义了类型为“pointer to char”的p,并将其初始化为指向类型为“array of char”、长度为4的对象,该对象的元素是用字符串文字初始化的。如果试图使用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

fhg3lkii

fhg3lkii4#

你不能改变一个字符串常量的内容,这是第一个p指向的。第二个p是一个用字符串常量初始化的数组,你可以改变它的内容。

xzlaal3s

xzlaal3s5#

对于这样的情况,效果是一样的:最终传递的是字符串中第一个字符的地址。
但声明显然是不一样的。
下面的代码为字符串和字符指针留出内存,然后将指针重新定位为指向字符串中的第一个字符。

char *p = "hello";

字符串
而下面的代码只为字符串留出内存。所以它实际上可以使用更少的内存。

char p[10] = "hello";

6fe3ivhb

6fe3ivhb6#

来自 APUE,第5.14节:

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

字符串
.对于第一个模板,名称在堆栈上分配,因为我们使用数组变量。然而,对于第二个名称,我们使用指针。在这种情况下,只有指针本身的内存驻留在堆栈上;编译器将字符串存储在可执行文件的只读段中。当mkstemp函数试图修改字符串时,会发生分段错误。
引用的文字与@Ciro Bullli的解释相符。

7ivaypg9

7ivaypg97#

在我的记忆中,数组实际上是一组指针。

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

字符串
是一个真实的说法

zkure5ic

zkure5ic8#

char p[3] = "hello"?应该是char p[6] = "hello"记住,在C中,“字符串”的末尾有一个“\0”字符。
在C语言中,数组只是一个指向内存中调整对象的第一个对象的指针。唯一不同的是在语义上。虽然你可以改变指针的值来指向内存中的不同位置,但数组在创建后,将始终指向相同的位置。
另外,当使用数组时,“新建”和“删除”会自动为您完成。

wfveoks0

wfveoks09#

看起来一样,但有细微的差别。
下面是char数组的声明:

char p[] = "hello"

字符串
这意味着p是一个右值,这意味着你可以使用p作为这个数组的别名。但实际上你可以使用p作为数组的地址(或数组的第一个元素)。但你不能像对指针那样对它进行操作,你不能这样做:

p++;


作为所有初始化(全局)变量,它将被放置在ELF文件的.data部分:

Hex dump of section '.data':
  0x00004000 00000000 00000000 08400000 00000000 .........@......
  0x00004010 68656c6c 6f00                       hello.

    ...


在这种情况下,我们也声明了char指针并初始化它:

char *p = "hello"


但是用于初始化的字符串文字将放在. rodata部分,而字符指针本身将放在ELF文件的.data部分:

Hex dump of section '.rodata':
  0x00002000 01000200 68656c6c 6f00              ....hello.

相关问题