C语言静态初始化器相互引用问题

8
在C语言中,是否可以通过互相引用的静态变量初始化器来实现下面示例中所示的功能呢?
如果在第二行添加对“B”的预声明,则此示例将编译通过,而且gcc -Wall不会给出警告。但由于第四行已经定义了B,因此第二行显得有些多余。使用 -weak 选项运行 Splint lint 程序时,会发出警告:“变量B被重新定义。函数或变量已被重新定义。其中一个声明应该使用 extern。”
通常情况下,您需要使用extern关键字进行声明,但是extern和static不能同时使用,如果在gcc下编译,将无法通过。
#include <stdio.h>                               /*1*/
volatile static void * B;                        /*2*/
volatile static void * A = &B;                   /*3*/
volatile static void * B = &A;                   /*4*/
int main()                                       /*5*/
{                                                /*6*/
    printf("A = %x, B = %x\n", (int)A, (int)B);  /*7*/
    return 0;                                    /*8*/
}                                                /*9*/

谢谢你。


3
我不知道为什么你需要这个? - blackcatweb
1
这是一个简化的例子。在我的嵌入式应用程序中,这种技术初始化了一个菜单链表,显示在一个20x4的LCD屏幕上。每个菜单都有指向相邻菜单的指针,用于导航。 - a b
作为一个非问题回答的旁白,将菜单存储在数组中并通过索引链接而不是指针可能更简单。 - Useless
听起来像splint不理解试探性定义。我建议您向开发人员提交错误报告。 - Adam Rosenfield
3个回答

5
尽管volatilestatic的关系有些奇怪,但您发布的代码完全是有效的 C 语言。它使用了一种名为tentative definitions的 C 语言特性。这个特性确保您的程序中只有一个B: 两个B的定义定义了相同的实体。这并没有什么"不好的"地方。
splint 给出的警告是无效的。在 C++ 语言中,这确实构成了多重定义错误,但在 C 语言中不是这样。关于extern的注释在 C 语言的上下文中根本毫无意义。

K&R2(A10.2)仅为外部对象定义了试探性定义,而不是静态定义。然而,更新的ISO规范通过明确允许静态变量的试探性定义来支持这个答案。 - a b
@a b:我确信这不是这种情况。您误解了此上下文中“external”一词的含义。“External”并不代表外部链接。在原始的 ANSI C89/90(以及我确定的 K&R2)的“6.7.2 External object definitions”的标题中,“external”一词指的是在文件范围内定义的对象,而不是在函数外部定义的对象。“External”在这种情况下只是意味着非局部的。C89/90中的6.7.2明确允许对使用static声明的对象进行试探性定义。尽管K&R2是在1988年出版的,但它与C89/90保持同步,因此应该说明相同的事情。 - AnT stands with Russia

3

这没有意义。

编辑:

是的,在这里不需要使用“extern”(感谢AndreyT和Adam Rosenfield),但是&B的类型是void **,而不是void *

当然,void **可以转换为void *,但是有什么意义呢?如果您想要别名或彼此之间的指针,则只需声明第三个变量“缓冲区”,并在A和B中指向它即可。

unsigned char SomeBuffer[LENGTH];

void* A = SomeBuffer;
void* B = SomeBuffer;

1
extern 会使它具有外部链接而不是内部链接。这不是重新声明,实际上它被称为“试探性定义”,并且是完全正确的 C 语言。 - Adam Rosenfield
extern 可能有助于实现什么目标?代码本身是完全有效的。 - AnT stands with Russia
@AndreyT:我不知道OP真正想要实现什么,所以我建议指向一些实际的容器。 "extern"关键字没有帮助,我会修复答案。 - Viktor Latypov

0

看起来你定义了一个循环,但实际上并没有。C中的&运算符是取地址运算符,可以获取所需变量的地址。

正如@AndreyT指出的那样,第2行的效果是暂时定义B,以便第3行知道它。你可以认为第2行分配了B的内存位置,然后在第4行将一个值放在那里。

这段代码的功能与您写成以下形式的代码相同:

volatile static void * A;
volatile static void * B;
int main()
{
    A = &B;
    B = &A;
    printf("A = %x, B = %x\n", (int)A, (int)B);
    return 0;
}                    

所以,在第3行中,您定义A指向B的地址。在第4行中,您定义B指向A的地址。
假设AB具有以下内存地址:
&A == 0xAAAAAAAA
&B == 0xBBBBBBBB

第3行的代码执行以下操作:
A = 0xBBBBBBBB;

然后在第4行执行以下操作:

B = 0xAAAAAAAA;

现在,如果您要取消引用AB,则会得到以下结果(注意,您必须首先将其转换为可取消引用的指针类型):

*A --> 0xAAAAAAAA
*B --> 0xBBBBBBBB

这是完全有效的,但可能不是您打算用代码实现的。

请记住,有两个不同的值在发挥作用。第一个是指针的值。第二个是指针的地址。

使用extern的唯一原因是使用在另一个对象文件中定义的变量(例如,在file1.c中,您想要使用在file2.c中定义的全局变量)。当应用于全局变量时,static关键字意味着变量是文件静态或仅可在该文件中使用。因此,这两者显然是相互矛盾的。


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