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

时间:2017-09-14 15:21:57

标签: c arrays pointers extern

我有两个文件:

在文件 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;
}

3 个答案:

答案 0 :(得分:13)

您的两个例子无法比较。

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

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

所以a是一个数组,而p是一个指针。在图片中绘制它看起来像

      +---+---+---+---+---+---+---+---+
   a: | a | b | c | d | e | f | g | \0|
      +---+---+---+---+---+---+---+---+
        ^
        |
   +----|----+
p: |    *    |
   +---------+

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

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

   +---+---+---+---+---+---+---+---+
p: | a | b | c | d | e | f | g | \0|
   +---+---+---+---+---+---+---+---+

您可以命名数组&#34; p&#34;如果你愿意(编译器当然不关心),但是,在文件0.c中,你改变主意并声明p是一个指针。您还声明(使用&#34; extern&#34;关键字)p在其他位置定义。所以编译器会接受你的话,并发出去往位置p的代码,并期望在那里找到一个指针 - 或者,在图片中,它希望找到一个包含箭头的框,指向其他地方。但它实际上发现了你的字符串"abcdefg",只是它没有意识到它。它可能最终会尝试将字节0x61 0x62 0x63 0x64(即构成字符串"abcdefg"的第一部分的字节)解释为指针。显然这不起作用。

如果您将printf中的0.c来电更改为

,则可以清楚地看到此信息
printf("%p\n", p);

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

0x67666564636261

字符串"abcdefg\0"的所有8个字节,顺序相反。 (从这里我们可以推断出我在一台机器上,(a)使用64位指针而(b)是小端。)所以如果我试图打印

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

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

还有两件事。

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

char *p = a;
在你的第二个例子中,我说的那个是#34;一切都很好;没问题&#34;? 如何将右侧的数组分配给左侧的指针? 答案是着名的(臭名昭着的?)&#34; C&#34中数组和指针之间的等价性:实际发生的事情就像你说的那样

char *p = &a[0];

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

当你问,&#34;为什么它不起作用,虽然它在这里工作?&#34;,还有另外两种方法你可以问它。 假设我们有两个函数

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]);

实际传递给函数的是指针,即 功能期望什么,所以我们很好。

但是另一种情况呢,我们传递一个指向函数的指针,该函数被声明为接受一个数组?嗯,实际上还有另一个原则,即C&#34;中数组和指针之间的等价性。 我们写的时候

void print_char_array(char a[])

编译对待它就像我们写了

一样
void print_char_array(char *a)

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

(而且,非常清楚,当我们谈论&#34;等价时 在C&#34;中的数组和指针之间,我们不是这么说的 指针和数组等价的,就是有这个 它们之间的特殊等价关系。我已经提到了 这种等同的两个原则已经存在。这都是 其中三个,供参考:(1)每当你 提到表达式中数组的名称,你是什么 自动获取是指向数组的第一个元素的指针。 (2)每当你声明一个似乎接受的函数时 数组,它实际接受的是一个指针。 (3)无论何时 使用&#34;数组&#34;订阅运算符[],指针,如 p[i],你实际获得的就像你写过*(p + i)一样。而且,事实上,如果你仔细考虑,由于 tenet(1),即使你使用数组下标运算符 看起来像阵列的东西,你真的在​​它上面使用它 指针。但这是一个非常奇怪的概念,你不会这样 不得不担心,如果你不想,因为它只是有效。)

答案 1 :(得分:4)

因为数组不是指针。你告诉程序“别处我有一个字符指针”,但你实际上没有一个 - 你有一个数组。

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

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

答案 2 :(得分:4)

让我反过来解释一下:

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

通过指针访问涉及间接存储器地址(“打印此指针指向的第3个字节”与“打印此数组的第3个字节”)。

在第一种情况下,你在其他地方有一个数组,但告诉编译器你在那个地方有一个指针。因此它尝试读取该指针并从其指向的位置读取数据。但是没有指针 - 立即有数据,因此指针指向“任何地方,无处可去”(至少很可能)。这构成了未定义的行为(通常缩写为UB)。