这些C语言外部定义有什么区别?

3

我在调试AVR微控制器时遇到了这个问题: 我有一个main.c文件,其中有许多变量定义,其中包括一个结构体数组,如下所示:

struct mystruct mystruct_array[COUNT];

在另一个 .c 文件中,我将此数组称为外部数组,但我省略了数组括号和大小,这样我就不必重复自己,只需将变量声明为指针(因为数组本质上是指针,对吧?):

extern struct mystruct *mystruct_array;

但是当我使用printf("%p\n", mystruct_array);检查数组的地址时,我得到了一个空指针,而不是数组在内存中的位置。如果我访问数组中后续的项,例如printf("%p\n", &(mystruct_array[n]));,它会打印地址0加上n倍的sizeof(struct mystruct)
只有在我将定义更改为:
extern struct mystruct mystruct_array[COUNT];

(和main.c中完全一样),我得到了数组的真正地址。

我的问题是:这对编译器有什么影响(在我的情况下是avr-gcc)?


3
数组不是指针,指针也不是数组。花一个周末在湖边喝杯咖啡思考这个问题,你的余生将会非常美好。 - Kerrek SB
@Kerrek SB:抱歉,我刚才忘记了当我将我的特定代码更改为这个更通用的示例时。我指的是mystruct_array - gietljohannes
1
没问题,放心。关于未来的问题,请记住:在发布之前重新阅读 :-) - Kerrek SB
1
如果在定义结构体的文件之外暴露COUNT不方便,您可以合理地声明extern struct mystruct mystruct_array[];。当然,避免使用全局变量通常是一个好主意。实际上,extern声明应该在一个头文件中,该头文件同时包含在main.cother.c文件中。这样可以正确交叉检查类型。另请参见如何在C语言中使用extern共享源文件之间的变量? - Jonathan Leffler
3个回答

8

这是一个有趣的问题。

当你写下:

struct mystruct mystruct_array[COUNT];

你需要创建一个全局数组,其中包含mystruct结构体,共有COUNT个元素。由于你没有初始化它,因此它将被填充为零。
然后,当你进行以下操作时:
extern struct mystruct *mystruct_array;

你告诉编译器有一个叫做mystruct_array的变量,它是一个指针。但实际上它是一个数组。因此,编译器会把数组的内容看作指针。
当你尝试输出该指针的值时,编译器会在内存中获取mystruct_array并将其内容输出,就好像它是一个指针。由于实际上它是一个全是零的数组,所以你看到的是一个空指针。
相反,你应该这样写:
extern struct mystruct mystruct_array[];

所以编译器知道你变量的正确类型。你可以在方括号中指定长度,但不必这样做。

我建议你阅读一下指针和数组之间的区别,以确保你将来不会混淆它们。


1
总之,这个程序是不规范的,因为它用不同的类型声明了相同的名称。 - Kerrek SB
1
Yes - tux3
@JoeZocker 基本上,数组只是内存中的一块数据。指针是一个数字,告诉你在哪里找到那块数据。让很多人困惑的部分是数组可以退化成指针。我无法在评论中为您编写完整的教程,但请确保“extern…”中的类型与定义的类型匹配,然后您就可以了。 - tux3
当我首次将数组声明为 struct mystruct mystruct_array[] 后,我可以随后仅通过 mystruct_array 引用第一个元素的地址。如果我想要获取该内容(在此情况下为零),我可以对该指针进行解引用操作(*mystruct_array == mystruct_array[0])。那么为什么数组符号与指针符号不同呢? - gietljohannes
我也可以简单地将第一个元素的地址称为mystruct_array,这就是“数组衰减为指针”的部分。当您在表达式中使用数组名称时,它会神奇地变成指向第一个元素的指针。但是当您向编译器声明数组时,您需要将其声明为数组。符号不同是因为它们不是相同的东西。只是有时候数组可以衰减为指针。 - tux3
显示剩余4条评论

2

最佳的方法是在头文件中放置一个extern声明,然后在一个文件中进行定义。例如:

header.h

``` extern int variable; ```

file.c

``` #include "header.h" int variable = 42; ```
extern struct mystruct mystruct_array[];
/* or extern struct mystruct mystruct_array[COUNT] */

main.c

#include "header.h"

#define COUNT 10
/* should have an initializer to be a definition: */
struct mystruct mystruct_array[COUNT] = { 0 };

/* ... */

other.c

#include "header.h"

/* ... */
    printf("%p\n", mystruct_array);

这样可以避免重复,限制需要进行更改的地方。请注意,如果您的头文件没有定义数组中元素的数量,则在提供数组定义的文件之外的文件中无法对该数组应用sizeof操作。

请注意,虽然数组不是指针,但在C源代码的大多数情况下,数组名会转换为指向数组第一个元素的指针。这就是数组被误解为指针的原因。虽然它们不是指针,但在许多方面它们表现得像指针。


0

extern的声明意味着所声明的对象在另一个C文件中是全局的。因此,当您生成对象文件时,即使所声明的对象(在本例中为结构)不存在,它也会被生成。

如果您声明:

extern struct structname * s;

这意味着在另一个C模块中有一个全局可见的指向结构体structname s的指针。

如果你声明:

extern struct structname s;

这意味着在另一个C模块中有一个全局可见的结构体structname s!

当您链接程序时,如果没有指示链接器链接包含该结构体的对象,则会出现未定义引用错误!


1
有趣的是,您没有涵盖问题所涉及的数组情况。 - Jonathan Leffler
我不否认我一直更喜欢将其称为指针,并且在解释如何将它们声明为数组之前,我应该尝试一下 :p ... :) 但是因为数组的名称就是一个指针... 它是一样的!!! - Sir Jo Black
我对AVR的使用非常感兴趣!我开始使用它们,感受到了它们的强大!...现在我正在评估8位微控制器:tiny 85/84,mega 168/328。我希望它们能成为我的新工作! - Sir Jo Black

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