C “中声明的静态函数”;";标头档

C “中声明的静态函数”;";标头档,c,function,static,internal,linkage,C,Function,Static,Internal,Linkage,对我来说,在源文件(我指的是.c文件)中定义和声明静态函数是一条规则 然而,在非常罕见的情况下,我看到人们在头文件中声明它。 由于静态函数有内部链接,我们需要在每个文件中定义它,我们包括声明函数的头文件。这看起来很奇怪,与我们通常在声明某个东西为静态时想要的相去甚远 另一方面,如果一个幼稚的人试图在没有定义的情况下使用该函数,编译器会抱怨。所以从某种意义上说,即使听起来很奇怪,这样做也不是很不安全 我的问题是: 在头文件中声明静态函数有什么问题 风险是什么 对编译时间有什么影响 运行时是否存在

对我来说,在源文件(我指的是.c文件)中定义和声明静态函数是一条规则

然而,在非常罕见的情况下,我看到人们在头文件中声明它。 由于静态函数有内部链接,我们需要在每个文件中定义它,我们包括声明函数的头文件。这看起来很奇怪,与我们通常在声明某个东西为静态时想要的相去甚远

另一方面,如果一个幼稚的人试图在没有定义的情况下使用该函数,编译器会抱怨。所以从某种意义上说,即使听起来很奇怪,这样做也不是很不安全

我的问题是:

  • 在头文件中声明静态函数有什么问题
  • 风险是什么
  • 对编译时间有什么影响
  • 运行时是否存在任何风险
为什么要同时使用全局和静态功能?在c语言中,函数默认为全局函数。如果要将对函数的访问限制为声明的文件,则只能使用静态函数。因此,您可以通过声明它为静态来主动限制访问


头文件中实现的唯一要求是C++模板函数和模板类成员函数。

首先,我想澄清我对您描述的情况的理解:头包含(仅)静态函数声明,而C文件包含定义,即函数的源代码。比如说

some.h:

static void f();
// potentially more declarations
#include "some.h"
static void f() { printf("Hello world\n"); }
// more code, some of it potentially using f()
some.c:

static void f();
// potentially more declarations
#include "some.h"
static void f() { printf("Hello world\n"); }
// more code, some of it potentially using f()
如果这是你描述的情况,我不同意你的说法

由于静态函数有内部链接,我们需要在每个文件中定义它,我们包括声明函数的头文件

如果您声明了函数,但没有在给定的翻译单元中使用它,我认为您不必定义它。gcc接受这一点,并发出警告;标准似乎并不禁止它,除非我遗漏了什么。这在您的场景中可能很重要,因为不使用函数但包含带有声明的标头的转换单元不必提供未使用的定义。
现在让我们来看看问题:

  • 在头文件中声明静态函数有什么问题?
    这有点不寻常。通常,静态函数是仅在一个文件中需要的函数。它们被声明为静态的,通过限制它们的可见性使其显式化。因此,在标题中声明它们有些矛盾。如果函数确实在多个定义相同的文件中使用,则应使用单个定义将其设置为外部。如果只有一个翻译单元实际使用它,则声明不属于标头。

    因此,一种可能的方案是确保各个翻译单元中不同实现的统一函数签名。对于C(和C++)中的不同返回类型,公共头会导致编译时错误;不同的参数类型只会在C中导致编译时错误(但在C++中不会,因为函数重载)
  • 风险是什么?
    我不认为你的情况有风险。(与在头中包含函数定义相反,它可能违反封装原则。)
  • 对编译时间有什么影响?
    函数声明很小,而且复杂度很低,因此在头中添加额外函数声明的开销可能可以忽略不计。但是,如果在许多翻译单元中为声明创建并包含一个额外的头,那么文件处理开销可能会很大(即,编译器在等待头i/O时会大量空闲)
  • 运行时是否存在任何风险?
    我看不到任何风险

这不是对上述问题的回答,但希望能说明为什么可以在头文件中实现
静态
(或
静态内联
)函数

我个人认为在头文件中声明某些函数
static
,只有两个很好的理由:


  • 如果头文件完全实现了仅在当前编译单元中可见的接口

    这是极为罕见的,但在一些示例库开发过程中的某个时刻,可能在教育环境中有用;或者,当用最少的代码与另一种编程语言交互时

    如果库或接口实现非常简单,并且(对于使用头文件的开发人员来说)易用性比代码大小更重要,那么开发人员可能会选择这样做。在这些情况下,头文件中的声明通常使用预处理器宏,允许多次包含相同的头文件,从而在C中提供某种粗糙的多态性

    这里有一个实际的例子:为线性全等伪随机数生成器在脚部操场上射击自己。由于实现是编译单元的本地实现,因此每个编译单元将获得自己的PRNG副本。这个例子还展示了如何在C中实现粗糙的多态性

    prng32.h

    #if defined(PRNG_NAME) && defined(PRNG_MULTIPLIER) && defined(PRNG_CONSTANT) && defined(PRNG_MODULUS)
    #define MERGE3_(a,b,c) a ## b ## c
    #define MERGE3(a,b,c) MERGE3_(a,b,c)
    #define NAME(name) MERGE3(PRNG_NAME, _, name)
    
    static uint32_t NAME(state) = 0U;
    
    static uint32_t NAME(next)(void)
    {
        NAME(state) = ((uint64_t)PRNG_MULTIPLIER * (uint64_t)NAME(state) + (uint64_t)PRNG_CONSTANT) % (uint64_t)PRNG_MODULUS;
        return NAME(state);
    }
    
    #undef NAME
    #undef MERGE3
    #endif
    
    #undef PRNG_NAME
    #undef PRNG_MULTIPLIER
    #undef PRNG_CONSTANT
    #undef PRNG_MODULUS
    
    #include <stdlib.h>
    #include <stdint.h>
    #include <stdio.h>
    
    #define PRNG_NAME       glibc
    #define PRNG_MULTIPLIER 1103515245UL
    #define PRNG_CONSTANT   12345UL
    #define PRNG_MODULUS    2147483647UL
    #include "prng32.h"
    /* provides glibc_state and glibc_next() */
    
    #define PRNG_NAME       borland
    #define PRNG_MULTIPLIER 22695477UL
    #define PRNG_CONSTANT   1UL
    #define PRNG_MODULUS    2147483647UL
    #include "prng32.h"
    /* provides borland_state and borland_next() */
    
    int main(void)
    {
        int i;
    
        glibc_state = 1U;
        printf("glibc lcg: Seed %u\n", (unsigned int)glibc_state);
        for (i = 0; i < 10; i++)
            printf("%u, ", (unsigned int)glibc_next());
        printf("%u\n", (unsigned int)glibc_next());
    
        borland_state = 1U;
        printf("Borland lcg: Seed %u\n", (unsigned int)borland_state);
        for (i = 0; i < 10; i++)
            printf("%u, ", (unsigned int)borland_next());
        printf("%u\n", (unsigned int)borland_next());
    
        return EXIT_SUCCESS;
    }
    
    #ifndef   INBUFFER_H
    #define   INBUFFER_H
    
    typedef struct {
        unsigned char  *head;       /* Next buffered byte */
        unsigned char  *tail;       /* Next byte to be buffered */
        unsigned char  *ends;       /* data + size */
        unsigned char  *data;
        size_t          size;
        int             descriptor;
        unsigned int    status;     /* Bit mask */
    } inbuffer;
    #define INBUFFER_INIT { NULL, NULL, NULL, NULL, 0, -1, 0 }
    
    int inbuffer_open(inbuffer *, const char *);
    int inbuffer_close(inbuffer *);
    
    int inbuffer_skip_slow(inbuffer *, const size_t);
    int inbuffer_getc_slow(inbuffer *);
    
    static inline int inbuffer_skip(inbuffer *ib, const size_t n)
    {
        if (ib->head + n <= ib->tail) {
            ib->head += n;
            return 0;
        } else
            return inbuffer_skip_slow(ib, n);
    }
    
    static inline int inbuffer_getc(inbuffer *ib)
    {
        if (ib->head < ib->tail)
            return *(ib->head++);
        else
            return inbuffer_getc_slow(ib);
    }
    
    #endif /* INBUFFER_H */
    
    使用上面的示例,示例-prng32.h

    #if defined(PRNG_NAME) && defined(PRNG_MULTIPLIER) && defined(PRNG_CONSTANT) && defined(PRNG_MODULUS)
    #define MERGE3_(a,b,c) a ## b ## c
    #define MERGE3(a,b,c) MERGE3_(a,b,c)
    #define NAME(name) MERGE3(PRNG_NAME, _, name)
    
    static uint32_t NAME(state) = 0U;
    
    static uint32_t NAME(next)(void)
    {
        NAME(state) = ((uint64_t)PRNG_MULTIPLIER * (uint64_t)NAME(state) + (uint64_t)PRNG_CONSTANT) % (uint64_t)PRNG_MODULUS;
        return NAME(state);
    }
    
    #undef NAME
    #undef MERGE3
    #endif
    
    #undef PRNG_NAME
    #undef PRNG_MULTIPLIER
    #undef PRNG_CONSTANT
    #undef PRNG_MODULUS
    
    #include <stdlib.h>
    #include <stdint.h>
    #include <stdio.h>
    
    #define PRNG_NAME       glibc
    #define PRNG_MULTIPLIER 1103515245UL
    #define PRNG_CONSTANT   12345UL
    #define PRNG_MODULUS    2147483647UL
    #include "prng32.h"
    /* provides glibc_state and glibc_next() */
    
    #define PRNG_NAME       borland
    #define PRNG_MULTIPLIER 22695477UL
    #define PRNG_CONSTANT   1UL
    #define PRNG_MODULUS    2147483647UL
    #include "prng32.h"
    /* provides borland_state and borland_next() */
    
    int main(void)
    {
        int i;
    
        glibc_state = 1U;
        printf("glibc lcg: Seed %u\n", (unsigned int)glibc_state);
        for (i = 0; i < 10; i++)
            printf("%u, ", (unsigned int)glibc_next());
        printf("%u\n", (unsigned int)glibc_next());
    
        borland_state = 1U;
        printf("Borland lcg: Seed %u\n", (unsigned int)borland_state);
        for (i = 0; i < 10; i++)
            printf("%u, ", (unsigned int)borland_next());
        printf("%u\n", (unsigned int)borland_next());
    
        return EXIT_SUCCESS;
    }
    
    #ifndef   INBUFFER_H
    #define   INBUFFER_H
    
    typedef struct {
        unsigned char  *head;       /* Next buffered byte */
        unsigned char  *tail;       /* Next byte to be buffered */
        unsigned char  *ends;       /* data + size */
        unsigned char  *data;
        size_t          size;
        int             descriptor;
        unsigned int    status;     /* Bit mask */
    } inbuffer;
    #define INBUFFER_INIT { NULL, NULL, NULL, NULL, 0, -1, 0 }
    
    int inbuffer_open(inbuffer *, const char *);
    int inbuffer_close(inbuffer *);
    
    int inbuffer_skip_slow(inbuffer *, const size_t);
    int inbuffer_getc_slow(inbuffer *);
    
    static inline int inbuffer_skip(inbuffer *ib, const size_t n)
    {
        if (ib->head + n <= ib->tail) {
            ib->head += n;
            return 0;
        } else
            return inbuffer_skip_slow(ib, n);
    }
    
    static inline int inbuffer_getc(inbuffer *ib)
    {
        if (ib->head < ib->tail)
            return *(ib->head++);
        else
            return inbuffer_getc_slow(ib);
    }
    
    #endif /* INBUFFER_H */
    
    请注意,上面的
    inbuffer\u skip()
    inbuffer\u getc()
    不检查
    ib
    是否为非空;这是此类功能的典型特征。假设这些访问器函数处于“快速路径”,即经常调用。在这种情况下,甚至函数调用开销也很重要(使用
    静态内联
    函数可以避免,因为它们在调用站点的代码中是重复的)

    简单的访问器函数,如上面的
    inbuffer\u skip()
    inbuffer\u getc()
    ,也可以让编译器避免函数调用中涉及的寄存器移动,因为函数期望其参数位于特定寄存器或堆栈中,而