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

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

  • char *p = "hello"
    < /代码> < / >之前
    
    
    

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

  • char p[] = "hello"
    < /代码> < / >之前
    
    
    

    这是一个存储你好的数组

当我把这两个变量都传递给这个函数时,有什么不同?

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

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

你的例子函数printSomething需要一个指针,所以如果你试图像这样传递一个数组给它:

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

编译器会假装是你写的:

char s[10] = "hello";
printSomething(&s[0]);
在我的记忆中,数组实际上是一组指针。 例如

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

是一个真实的陈述

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

无论如何,C语言中的array只是指向内存中调整对象的第一个对象的指针。唯一不同的是语义。虽然可以将指针的值更改为指向内存中的不同位置,但创建后的数组将始终指向相同的位置 此外,当使用数组时,“new”和“delete”会自动为你完成

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

声明显然是不一样的。

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

char *p = "hello";

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

char p[10] = "hello";

让我们来看看:

#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常见问题解答中有一个相关的问题:

这些初始化之间的区别是什么?

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

如果我试图给p[I]赋一个新值,程序就会崩溃。

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

  1. 作为char数组的初始化式,就像在chara[]声明中一样,它指定了该数组中字符的初始值(如果需要,还指定了数组的大小)。
  2. 在其他任何地方,它会变成一个未命名的静态字符数组,这个未命名数组可能存储在只读内存中,因此不一定可以修改。在表达式上下文中,数组像往常一样被立即转换为指针(参见第6节),因此第二个声明将p初始化为指向未命名数组的第一个元素。

有些编译器有一个开关,控制字符串字面值是否可写(用于编译旧代码),有些编译器可能有选项,使字符串字面值被正式地视为const char数组(以便更好地捕捉错误)。

参见问题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节基本原理

H&S第2.7.4节第31-2页

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

在C语言中,char数组和char指针的区别是什么?

c99n1256草案

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

  1. < p >初始化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],你也修改了__unnamed,它是UB。

    这在6.4.5 "String literals"中有说明:

    在转换阶段7中,值为0的字节或代码被附加到每个多字节 由一个或多个字符串字面值产生的字符序列。多字节字符 然后使用Sequence初始化一个静态存储持续时间和长度的数组 足以包含序列。对于字符串字面量,数组元素具有 类型为char,并用多字节字符的单个字节进行初始化 序列[…]< / p > 如果数组的元素具有属性,则不指定这些数组是否不同 适当的值。如果程序试图修改这样的数组,行为为 定义。< / p > 李< /引用> < / >

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

定义类型为“指向char的指针”的p,并将其初始化为指向长度为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

APUE, Section 5.14:

char    good_template[] = "/tmp/dirXXXXXX"; /* right way */
char    *bad_template = "/tmp/dirXXXXXX";   /* wrong way*/
< p >…对于第一个模板,名称分配在堆栈上,因为我们使用 数组变量。然而,对于第二个名称,我们使用指针。在这种情况下,只有 指针本身的内存位于堆栈上;编译器将字符串安排为 存储在可执行文件的只读段中。当mkstemp函数尝试

.

.

.

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