header-only C 库中的全局单例

Global singleton in header-only C library

我正在尝试在 C(不是 C++)的 header-only 库中实现一个全局单例变量。因此,在这个论坛和其他地方搜索之后,我遇到了 Meyer 单例的变体,我正在这里适应 C:

    /* File: sing.h */
    #ifndef SING_H
    #define SING_H
    inline int * singleton()
    {
        static int foo = 0;
        return &foo;
    }
    #endif

请注意,我正在返回一个指针,因为 C 缺少在 C++ 中可用的引用,所以我必须解决它。

OK,现在我要测试一下,下面是一个简单的测试代码:

    /* File: side.h */
    #ifndef SIDE_H
    #define SIDE_H
    void side();
    #endif
    /*File: side.c*/
    #include "sing.h"
    #include <stdio.h>
    void side()
    {
        printf("%d\n",*(singleton()));
    }
    /*File: main.c*/
    #include "sing.h"
    #include "side.h"
    #include <stdio.h>
    int main(int argc, char * argv[])
    {
        /* Output default value - expected output: 0 */
        printf("%d\n",*(singleton()));
        *(singleton()) = 5;
        /* Output modified value - expected output: 5 */
        printf("%d\n",*(singleton()));
        /* Output the same value from another module - expected output: 5*/
        side();
        return 0;
    }

在 C 模式下的 MSVC 中编译和 运行s 很好(也在 C++ 模式下,但这不是主题)。但是,在 gcc 中,它输出两个警告(warning: 'foo' is static but in declared in inline function 'singleton' which is not static),并生成一个可执行文件,然后在我尝试时出现段错误到运行吧。警告本身对我来说有点意义(事实上,我很惊讶我没有在 MSVC 中得到它),但是段错误暗示了 gcc 永远不会将 foo 编译为静态变量的可能性,使其成为局部变量堆栈然后 returns 该变量的过期堆栈地址。

我尝试将单例声明为 extern inline,它编译并在 MSVC 中 运行 正常,导致 gcc 中的链接器错误(同样,我不抱怨链接器错误,它是逻辑)。 我还尝试了 static inline(在 MSVC 和 gcc 中编译都很好,但可以预见的是 运行s 在第三行输出错误,因为 side.c 翻译单元现在有它自己的单例副本。

那么,我在 gcc 中做错了什么?我在 C++ 中没有这些问题,但在这种情况下我不能使用 C++,它必须是直接的 C 解决方案。

我也可以接受在 gcc 和 MSVC 中直接从 header-only 库中使用的任何其他形式的单例实现。

I am trying to implement a global singleton variable in the header-only library in C (not C++).

这里的“全局”,我理解你的意思是“具有静态存储时长和外部链接”。至少,这是 C 所能达到的最接近的水平。这也是 C 可以接近 built-in 类型的“单例”的程度,因此从这个意义上说,术语“全局单例”是多余的。

Notice that I am returning a pointer because C lacks & referencing available in C++, so I must work around it.

C 没有引用是正确的,但如果您不使用函数来包装对 object 的访问,则不需要指针或引用。我并没有真正看到你试图通过它获得什么。你可能会发现没有它更容易得到你想要的东西。例如,当面对同一变量标识符的重复外部定义时,除了最新版本的 GCC 之外,所有的默认行为都是将它们合并到一个变量中。尽管当前的 GCC 将这种情况报告为错误,但通过打开 command-line 开关,旧行为仍然可用。

另一方面,您的内联函数方法不太可能适用于许多 C 实现。请特别注意 inline 语义在 C 中与在 C++ 中有很大不同,特别是外部内联函数在 C 中很少有用。考虑 C 标准的这些规定:

  • 第 6.7.4/3 段(语言限制):

    An inline definition of a function with external linkage shall not contain a definition of a modifiable object with static or thread storage duration, and shall not contain a reference to an identifier with internal linkage.

    因此您的示例代码是 non-conforming,并且需要符合标准的编译器来对其进行诊断。尽管如此,他们可能会接受你的代码,但他们可以用它做任何他们选择的事情。期望您可以依赖随机符合 C 的实现来接受您的函数代码并编译它,以便不同翻译单元中的调用者可以通过调用该函数获得指向相同 object 的指针,这似乎是不合理的希望。

  • 第 6.9/5 段:

    An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. If an identifier declared with external linkage is used in an expression [...], somewhere in the entire program there shall be exactly one external definition for the identifier [...].

    请注意,尽管带有外部链接的函数标识符的内联定义(例如您的定义)提供了该标识符的外部 声明,但它不提供外部链接它的定义。这意味着程序中的某处需要单独的外部定义(除非该函数完全未被使用)。此外,该外部定义不能位于包含内联定义的翻译单元中。这是 extern 内联函数在 C 中很少有用的原因之一。

  • 第 6.7.4/7 段:

    For a function with external linkage, the following restrictions apply: [...] If all of the file scope declarations for a function in a translation unit include the inline function specifier without extern, then the definition in that translation unit is an inline definition. An inline definition does not provide an external definition for the function, and does not forbid an external definition in another translation unit. An inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit. It is unspecified whether a call to the function uses the inline definition or the external definition.

    除了回应 6.9/5 的一部分之外,这还警告您,如果您确实提供了函数的外部定义以配合内联定义,则您无法确定哪个将用于任何特定调用.

  • 此外,您不能通过声明具有内部链接的函数来解决这些问题,因为尽管这允许您在其中声明一个静态变量,但函数的每个定义都是一个不同的函数。为了避免任何疑问,脚注 140 澄清了在这种情况下,

    Since an inline definition is distinct from the corresponding external definition and from any other corresponding inline definitions in other translation units, all corresponding objects with static storage duration are also distinct in each of the definitions.

(强调已添加。)

因此,您的示例中提供的方法不能依赖于在 C 中工作,尽管您可能会发现在实践中它确实适用于某些编译器。


如果你需要它成为一个 header-only 库,那么你可以通过对你的用户提出额外要求以可移植的方式实现它:在任何使用你的 [=69= 的程序中只有一个翻译单元] 库必须在包含 header 之前定义一个特殊的宏。例如:

/* File: sing.h */
#ifndef SING_H
#define SING_H

#ifdef SING_MASTER

int singleton = 0;

#else

extern int singleton;

#endif
#endif

因此,在包含 sing.h(第一次)之前定义 SING_MASTER 的一个翻译单元将提供 singleton 所需的定义,而所有其他翻译单元将只有一个声明。此外,变量可以直接访问,无需调用函数或取消引用指针。