变量的外部指针 vs 数组

4
我知道这个话题已经被提到了几次,但是我还是不理解。
涉及http://c-faq.com/aryptr/aryptr2.html
char a[] = "hello";
char *p = "world";

创建一个名为"a"的数组。该变量只是指向第一个内存地址的标签。 p是一个变量(==另一个内存地址的标签),包含第一个字符(w - 可能在内存中的其他地方)的地址。

我不明白这个主题中的例子(谢谢!): 使用extern将数组与指针链接

extern1.c

extern int *array;
int test();

int main(int argc, char *argv[])
{
    printf ("in main: array address = %x\n", array);
    test();
    return 0;
}

extern2.c

int array[10] = {1, 2, 3};

int test()
{
    printf ("in test: array address = %x\n", array);
    return 0;
}

为什么extern1中的指针变量"extern int *array"(相当于一个内存地址标签,包含另一个地址)已经包含了array[0]的内容?

与此有何区别?

int array[10] = {1, 2, 3};
int *ptrArrWorking = array; // == &array[0];

在外部情况下,它可能是这样的。
int array[10] = {1, 2, 3};
int *ptrArrNotWorking = array[0]; 

编辑:为了更明确,让我们将上面的示例解释为

int array[10] = {1, 2, 3};
int *ptrArrNotWorking = (int *)array[0]; 

因此,这模拟了在外部示例中可以看到的相同行为。

间接寻址在哪里隐藏?

非常感谢。


在第二种情况下,您应该编写&array [0],这与array相同。 - Ajay Brahmakshatriya
在 extern 的情况下它能够工作是因为类型被定义为 int*,并且名称与数组相同。因此它们是同一物体。 - Ajay Brahmakshatriya
*ptrArrWorking = array = &array[0],这不是正确的吗?这已经在行注释中了。 如果它们是相同的东西,为什么指针指向第一个元素而不是整个数组? - Jan
是的,你的第一个情况是正确的。评论也是正确的。但是你对extern情况的解释是不正确的。 - Ajay Brahmakshatriya
3个回答

5

您在extern2.c中定义了一个名为array的对象:

int array[10] = {1, 2, 3};

这是一个包含十个 int 的连续内存段。在将其传递给 test 中的 printf 时,它会衰减为指向其第一个元素的指针 - 这就是数组传递给函数的方式。因此,printf 打印第一个 int 的地址。

然而,在 extern1.c 中,当你再次声明 array 时却撒了谎:

extern int *array;

这个代码假定 array 是一个指针,一个保存其他东西地址的单个对象。这种不匹配使程序“不合规范,无需诊断”。这是标准术语,意思是“严重破坏”——从此程序是否编译,以及在运行时实际执行什么都没有要求。
实际上,在那个错误的声明后面的代码确实会将 array 视为指针。因此,当你将 array 传递给 printf 时,它将从数组开头截取一些字节(通常是4或8,具体取决于平台),大喊“这就是指针”,然后将其提供给 printf
当然,它实际上不是有效的指针,而只是拼凑在一起的前几个 int 的位,因此你看到的是无意义的东西。

这是对正在发生的事情的正确解释。如果您将array的类型更改为int8_t,它将变得更加清晰。 - LennyB
非常好,谢谢。因此,程序的行为是未定义和破碎的。总之,所有答案都有道理,我会接受这个答案,因为它对我来说最清晰。 非常感谢大家! - Jan
@LennyB,我将数组的类型更改为另一种数据类型,并看到了你的意思。指针只是抓取了数组的前4个字节。重点是数组的(正确)衰减与指针的非衰减(因为它已经是指针类型)。这是由于欺骗编译器造成的,是吗? - Jan
@Jan 是的,代码的行为取决于它“认为”的array的类型。也不需要指针或数组:你可以通过在一侧声明一个int,在另一侧声明一个double,并获得一半混乱的double来触发相同的错误。 - Quentin

0

extern 只是声明变量,但不定义变量

这意味着 array 变量(在 extern1.c 中声明为 extern)实际上只是在 extern2.c 中定义的变量 array 的别名。


这可能是关键点。这怎么解释呢?我仍然不明白,额外的间接引用是从哪里来的。它是否有技术原因(在汇编/架构层面上)或者是编译器/ C 标准的约定?顺便问一下,它是否有效,还是实现定义或者未定义行为? - Jan

0
创建一个名为“a”的数组。变量只是第一个内存地址的标签。
不,a是整个数组hello\0。每当在表达式中使用标识符a时,数组就会“衰减”为指向其第一个元素的指针。也就是说,在使用a的表达式中,您将得到一个临时的char*指向字母'h'。
可以这样想:数组就像通往罗马的道路。您问编译器“通往罗马的道路在哪里,我需要访问它”,然后它会乐意地为您设置一个临时的路标。这并不意味着通往罗马的道路变成了路标。也不意味着路标本身就是通往罗马的道路。同样,仅因为您正在驾驶(访问)它,通往罗马的道路并不会变成路标。
为什么extern1中的指针变量“extern int *array”(==内存地址的标签,包含其他地址)已经包含array[0]的内容?

不行。你不能写extern int* array并期望得到一个指向在其他地方分配的数组int array[10]中第一个项目的指针。因为数组不是指针,指针也不是数组。

在示例中发生的情况是代码调用未定义的行为(错误),程序员欺骗编译器并说“存储在这里的实际上是指针,请相信我”,即使那里没有指针,而是整数。

所以在特定系统上发生的情况是,您得到指针地址设置为1。而不是指向的数据。这是您无法依赖的事情;如果指针和整数在给定系统上具有不同的大小或表示,则代码可能会崩溃。

使用关于道路的类比,您的编译器是司机,CPU的程序计数器是汽车。使用extern int *array告诉编译器“那边的东西是路标!”,指的是实际的道路。然后编译器盲目地按照您告诉它的方式尝试解释某些方向,并让汽车跟随它,之后它就会跑到荒野中,很可能会崩溃,肯定永远不会到达罗马。

在这里:

int array[10] = {1, 2, 3};
int *ptrArrWorking = array; // == &array[0];

你将指针设置为指向数组中的第一个项目。这意味着指针将获取该项存储的内存地址。你不会将地址本身设置为1,在大多数系统上这很可能是无意义的。

在extern情况下,它可能是这样的

int array[10] = {1, 2, 3};
int *ptrArrNotWorking = array[0]; 

没错,这段代码毫无意义。实际上,它甚至不是有效的 C 代码,因为你不能直接将整数赋值给指针,必须先进行转换 - 这可以通过类型转换来完成。


这里提到a只是第一个元素内存地址的标签。这是不正确的吗?我知道如果欺骗编译器会发生一些不好的事情。只是想知道真正发生了什么,以及是否存在未定义/实现定义问题。我在主要问题中添加了显式转换int *ptrArrNotWorking = array [0]; - Jan
@Jan 在C语言中,“类型数组”数据类型指的是整个内存块,而不仅仅是第一个地址。自然地,数组本身的地址将与第一个元素的地址相同。尽管该教程试图简化数组概念,但它似乎大部分是正确的。数组类型实际上还包含有关编译器如何确定数组大小的内部信息,并且它是一种独特的类型。 - Lundin
如果在表达式中使用数组,它是否也会衰变为指向第一个元素地址的指针?我知道在将其作为函数参数传递时是正确的。 - Jan
@Jan 我相信我的回答的第一句已经解决了这个问题。 - Lundin
是的,没错。只是想确认一下是否正确——因为我之前认为这只适用于参数传递。 我想给你点赞,但不允许:( 谢谢! - Jan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接