为什么这个简单的 C 程序会崩溃(数组 VS 指针)

Why this simple program in C crashes (array VS pointer)

我有两个文件:

在文件 1.c 中,我有以下数组:

char p[] = "abcdefg";

在文件0.c中我有以下代码:

#include <stdio.h>

extern char *p; /* declared as 'char p[] = "abcdefg";' in 1.c file */

int main()
{
    printf("%c\n", p[3]);   /* crash */
    return 0;
}

这是命令行:

gcc  -Wall -Wextra     0.c  1.c

我知道 extern char *p 应该是:extern char p[];,但我只想解释 为什么它在这种特殊情况下不起作用.虽然它在这里工作:

int main()
{
    char a[] = "abcdefg";
    char *p = a;

    printf("%c\n", p[3]);   /* d */
    return 0;
}

因为数组不是指针。你告诉程序 "elsewhere I have a char pointer",但你实际上没有 - 你有一个数组。

数组在表达式中使用时会退化为指针,但这并不意味着数组指针。有关详细信息,请参阅 Is an array name a pointer?

在你的第二个例子中,你有一个数组和一个指针,两个独立的变量,所以这是不同的情况。

我倒过来解释一下:

在第二种情况下,您有一个数组,然后是一个指向该数组的指针。

通过指针访问涉及间接内存地址("print the 3rd byte from where this pointer points to" 与 "print the 3rd byte of this array")。

在第一种情况下,你在其他地方有一个数组,但告诉编译器你在那个地方有一个指针。所以它试图读取该指针并从它指向的地方读取数据。但是没有指针——数据立即存在,所以指针指向 "anywhere and nowhere"(至少,很有可能)。这构成了未定义的行为(通常缩写为 UB)。

你的两个例子没有可比性。

在你的第二个例子中,你有

char a[] = "abcdefg";
char *p = a;

所以a是数组,p是指针。画在图片里,好像

      +---+---+---+---+---+---+---+---+
   a: | a | b | c | d | e | f | g | [=11=]|
      +---+---+---+---+---+---+---+---+
        ^
        |
   +----|----+
p: |    *    |
   +---------+

这一切都很好;该代码没有问题。

但是在您的第一个示例中,在文件 1.c 中您定义了一个名为 p:

的数组
   +---+---+---+---+---+---+---+---+
p: | a | b | c | d | e | f | g | [=12=]|
   +---+---+---+---+---+---+---+---+

如果需要,您可以将数组命名为“p”(编译器当然不关心),但是随后,在文件 0.c 中,您改变了主意并声明 p 是一个指针。您还声明(使用“extern”关键字)p 是在别处定义的。因此,编译器相信您的话,并发出前往位置 p 的代码,并期望在那里找到一个指针——或者,在图片中,它期望找到一个包含箭头的框,该框指向其他地方.但它实际找到的是您的字符串 "abcdefg",只是它没有意识到。它可能最终会尝试将字节 0x61 0x62 0x63 0x64(即构成字符串 "abcdefg" 的第一部分的字节)解释为指针。显然那是行不通的。

如果将 0.c 中的 printf 调用更改为

,您可以清楚地看到这一点
printf("%p\n", p);

这会将指针 p 的值打印为指针 。 (嗯,当然, p 并不是真正的指针,但你对编译器撒谎并告诉它它是,所以你将看到的是编译器将其视为指针时的结果,这是我们在这里试图理解的。)在我的系统上打印

0x67666564636261

这是字符串 "abcdefg[=43=]" 的全部 8 个字节,顺序相反。 (由此我们可以推断出我在一台机器上(a)使用 64 位指针并且(b)是小端。)所以如果我尝试打印

printf("%c\n", p[3]);

它会尝试从位置 0x67666564636264(即 0x67666564636261 + 3)获取一个字符并打印它。现在,我的机器有相当多的内存,但它没有 that 很多,所以位置 0x67666564636264 不存在,因此程序在尝试时崩溃从那里获取。

还有两件事。

如果数组与指针不同,你是怎么说的

char *p = a;

你的第二个例子,我说的是"all fine; no problems"? 如何将右侧的数组分配给左侧的指针? 答案是著名的(臭名昭著的?)"equivalence between arrays and pointers in C":实际发生的事情就像你说的那样

char *p = &a[0];

每当你在表达式中使用数组时,你得到的实际上是指向数组第一个元素的指针,就像我在这个答案的第一张图片中显示的那样。

当您提问 "why it doesn't work, while it works here?" 时,您可以通过其他两种方式提问。 假设我们有两个函数

void print_char_pointer(char *p)
{
    printf("%s\n", p);
}

void print_char_array(char a[])
{
    printf("%s\n", a);
}

然后假设我们回到你的第二个例子,

char a[] = "abcdefg";
char *p = a;

假设我们调用

print_char_pointer(a);

print_char_array(p);

如果你尝试一下,你会发现两者都没有问题。 但这怎么可能呢?我们如何将数组传递给 当我们调用 print_char_pointer(a) 时需要一个指针的函数? 我们如何将指针传递给 当我们调用 print_char_array(p)?

时需要一个数组的函数

记住,每当我们在表达式中提到数组时, 我们得到的是指向数组第一个元素的指针。所以当 我们叫

print_char_pointer(a);

我们得到的就像我们写的一样

print_char_pointer(&a[0]);

实际上传递给函数的是一个指针,它是 函数期望什么,所以我们很好。

但是另一种情况呢,我们将指针传递给一个声明为接受数组的函数?好吧,"equivalence between arrays and pointers in C" 实际上还有另一个原则。 当我们写

void print_char_array(char a[])

编译就像我们写的那样对待它

void print_char_array(char *a)

为什么编译器会做这样的事情?为什么,因为它知道 没有数组会被传递给函数,所以它知道没有 函数实际上会接收一个数组,所以它知道 函数将接收一个指针。所以这就是 编译器处理它。

(而且,非常清楚,当我们谈论“等价”时 在 C 中的数组和指针之间”,我们并不是说 指针和数组 等价的,只是有这个 它们之间存在特殊的等价关系。我提到过 该等效性的两个原则已经存在。这里都有 其中三个,供参考: (1) 每当你 在表达式中提及数组的名称,你 自动获取的是指向数组第一个元素的指针。 (2) 每当你声明一个似乎接受一个 数组,它实际接受的是一个指针。 (3) 每当你 在指针上使用 "array" 下标运算符 [],如 p[i],你实际得到的就好像你写了*(p + i)一样。而且,事实上,如果你仔细想想,由于 原则 (1),即使您在上使用数组下标运算符 一些看起来像数组的东西,你实际上是在 指针。但这是一个很奇怪的概念,你不知道 如果您不想,则不必担心,因为它确实有效。)