【问题标题】:Why this simple program in C crashes (array VS pointer)为什么这个简单的 C 语言程序会崩溃(数组 VS 指针)
【发布时间】:2017-09-14 15:21:56
【问题描述】:

我有两个文件:

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

【问题讨论】:

  • 所以你声明了一个具有两种冲突类型的变量并询问为什么它不起作用?
  • 1.cp是一个数组,在0.c是一个指针
  • 第二个例子完全不同。在第一个中,您欺骗了不知道p 类型的链接器,它还给了您。在第二个中,每个人都知道一切。
  • UB͏͏͏͏͏͏͏͏͏͏͏͏͏
  • @BiteBytes 第二个例子没有尝试撒谎并用两种不同的类型对待一个single id

标签: c arrays pointers extern


【解决方案1】:

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

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

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|
   +---+---+---+---+---+---+---+---+

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

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

printf("%p\n", p);

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

0x67666564636261

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

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

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

还有两件事。

如果数组和指针不一样,你是怎么做到的

char *p = a;

在你的第二个例子中,我说的是“一切都好;没有问题”? 如何将右侧的数组分配给左侧的指针? 答案是著名的(臭名昭著的?)“C 中数组和指针之间的等价性”:实际发生的事情就像你说过的一样

char *p = &a[0];

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

当你问“为什么它不起作用,而它在这里起作用?”时,你可以用另外两种方式问它。 假设我们有两个函数

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 中的数组和指针之间的等价性”实际上还有另一个原则。 我们写的时候

void print_char_array(char a[])

编译处理它就像我们写的一样

void print_char_array(char *a)

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

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

【讨论】:

    【解决方案2】:

    因为数组不是指针。你告诉程序“在其他地方我有一个 char 指针”,但实际上你没有 - 你有一个数组。

    数组在表达式中使用时会衰减为指针,但这并不意味着数组指针。欲了解更多信息,请参阅Is an array name a pointer?

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

    【讨论】:

      【解决方案3】:

      让我反过来解释一下:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-01
        • 2020-05-12
        • 2023-03-11
        相关资源
        最近更新 更多