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

die*_*sel 205 c arrays pointers

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

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

void printSomething(char *p)
{
    printf("p: %s",p);
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*Jon 215

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

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

char s[10] = "hello";
printSomething(s);
Run Code Online (Sandbox Code Playgroud)

编译器假装你写了这个:

char s[10] = "hello";
printSomething(&s[0]);
Run Code Online (Sandbox Code Playgroud)

  • @BhanuTez 不,数据如何存储以及如何处理数据是不同的问题。此示例打印整个字符串,因为这就是 `printf` 处理 `%s` 格式字符串的方式:从提供的地址开始并继续,直到遇到空终止符。例如,如果您只想打印一个字符,则可以使用“%c”格式字符串。 (4认同)

JJJ*_*JJJ 78

让我们来看看:

#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;
}
Run Code Online (Sandbox Code Playgroud)

foo*和foo []是不同的类型,它们由编译器处理不同(指针=地址+指针类型的表示,数组=指针+数组的可选长度,如果已知,例如,如果数组是静态分配的),细节可以在标准中找到.并且在运行时级别它们之间没有区别(在汇编程序中,好吧,差不多,见下文).

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

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

char a[] = "string literal";   
char *p  = "string literal";   
Run Code Online (Sandbox Code Playgroud)

如果我尝试为p [i]分配新值,我的程序会崩溃.

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

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

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

另见问题1.31,6.1,6.2,6.8和11.8b.

参考文献:K&R2 Sec.5.5 p.104

ISO秒 6.1.4,Sec.6.5.7

基本原理 3.1.4

H&S Sec.2.7.4 pp.31-2


Cir*_*四事件 30

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

C99 N1256草案

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

  1. 初始化char[]:

    char c[] = "abc";      
    
    Run Code Online (Sandbox Code Playgroud)

    这是"更神奇",并在6.7.8/14"初始化"中描述:

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

    所以这只是一个捷径:

    char c[] = {'a', 'b', 'c', '\0'};
    
    Run Code Online (Sandbox Code Playgroud)

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

  2. 其他地方:它产生一个:

    所以当你写:

    char *c = "abc";
    
    Run Code Online (Sandbox Code Playgroud)

    这类似于:

    /* __unnamed is magic because modifying it gives UB. */
    static char __unnamed[] = "abc";
    char *c = __unnamed;
    
    Run Code Online (Sandbox Code Playgroud)

    注意来自char[]to 的隐式转换char *,这总是合法的.

    然后,如果你修改c[0],你也修改__unnamed,这是UB.

    这在6.4.5"字符串文字"中记录:

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

    6如果这些数组的元素具有适当的值,则这些数组是否不同是未指定的.如果程序试图修改此类数组,则行为未定义.

6.7.8/32"初始化"给出了一个直接的例子:

例8:声明

char s[] = "abc", t[3] = "abc";
Run Code Online (Sandbox Code Playgroud)

定义"plain"char数组对象s,t其元素使用字符串文字初始化.

该声明与之相同

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
Run Code Online (Sandbox Code Playgroud)

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

char *p = "abc";
Run Code Online (Sandbox Code Playgroud)

p使用"指向char的指针"类型定义并将其初始化为指向类型为"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;
}
Run Code Online (Sandbox Code Playgroud)

编译和反编译:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
Run Code Online (Sandbox Code Playgroud)

输出包含:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata
Run Code Online (Sandbox Code Playgroud)

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

如果我们这样做char[]:

 char s[] = "abc";
Run Code Online (Sandbox Code Playgroud)

我们获得:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)
Run Code Online (Sandbox Code Playgroud)

所以它存储在堆栈中(相对于%rbp).

但是请注意,默认的链接脚本提出.rodata.text在同一网段,它具有执行,但没有写权限.这可以通过以下方式观察:

readelf -l a.out
Run Code Online (Sandbox Code Playgroud)

其中包含:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata
Run Code Online (Sandbox Code Playgroud)

  • UB是什么意思? (5认同)
  • @ leszek.hanusz未定义行为http://stackoverflow.com/questions/2766731/what-exactly-do-ib-and-ub-mean谷歌"C语言UB";-) (2认同)

pot*_*bie 9

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


Ric*_*ick 7

来自APUE,第 5.14 节:

char    good_template[] = "/tmp/dirXXXXXX"; /* right way */
char    *bad_template = "/tmp/dirXXXXXX";   /* wrong way*/
Run Code Online (Sandbox Code Playgroud)

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

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


Jon*_*ood 6

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

声明显然不一样.

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

char *p = "hello";
Run Code Online (Sandbox Code Playgroud)

虽然以下内容仅为字符串留出内存.所以它实际上可以使用更少的内存.

char p[10] = "hello";
Run Code Online (Sandbox Code Playgroud)