为什么这个简单的 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),即使您在上使用数组下标运算符
一些看起来像数组的东西,你实际上是在
指针。但这是一个很奇怪的概念,你不知道
如果您不想,则不必担心,因为它确实有效。)
我有两个文件:
在文件 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),即使您在上使用数组下标运算符
一些看起来像数组的东西,你实际上是在
指针。但这是一个很奇怪的概念,你不知道
如果您不想,则不必担心,因为它确实有效。)