我有两个文件:
在文件 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;
}
答案 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)。