章节大纲

  • 匹配外部定义与其声明

    尽管 K&R(Kernighan 和 Ritchie)定义了一个用于定义和引用外部对象的模型,但也有许多其他模型被广泛采用,这导致了一些混淆。以下各小节中将介绍这些不同的模型。

    标准 C 采用了一个结合了“严格引用/定义模型”和“初始化模型”的混合模型。这种方法旨在尽可能兼容各种环境和已有实现。

    标准 C 规定:如果一个具有外部链接的标识符在两个源文件中有不兼容的声明,则行为是未定义的。

    某些实现中,只要用户代码中声明了一个外部标识符,即使并未实际使用它,链接器也会将包含该标识符定义的目标模块加载到可执行映像中。而标准 C 规定:如果一个具有外部链接的标识符未在表达式中使用,则不必为它提供外部定义。换句话说,仅通过声明一个对象并不能强制它被加载!


    严格引用/定义模型(Strict ref/def Model)

    /* 源文件 1        源文件 2 */
    
    int i;             extern int i;
    int main()         void sub()
    {                  {
        i = 10;            /* … */
        /* … */        }
    }
    

    在这个模型中,对变量 i 的声明只能出现一次,并且不能带有 extern 关键字。其他对该外部变量的引用都必须使用 extern 关键字。此模型为 K&R 所定义的模型。


    宽松引用/定义模型(Relaxed ref/def Model)

    /* 源文件 1        源文件 2 */
    
    int i;             int i;
    int main()         void sub()
    {                  {
        i = 10;            /* … */;
        /* … */        }
    }
    

    在此模型中,两个对变量 i 的声明都不使用 extern 关键字。如果在某处使用了 extern 来声明该标识符,则必须在程序的其他地方出现一次实际的定义。如果带有初始化器进行声明,那么该初始化声明在整个程序中只能出现一次。这个模型在类 UNIX 系统中非常常见。虽然采用此模型的程序符合标准 C,但它们的可移植性并非最佳。


    通用模型(Common Model)

    /* 源文件 1        源文件 2 */
    
    extern int i;      extern int i;
    
    int main()         void sub()
    {                  {
        i = 10;            /* … */;
        /* … */        }
    }
    

    在该模型中,所有对外部变量 i 的声明都可以(也可以不)带上 extern 关键字。此模型模仿了 Fortran 中的 COMMON 块行为。


    初始化器模型(Initializer Model)

    /* 源文件 1        源文件 2 */
    
    int i = 0;         int i;
    
    int main()         void sub()
    {                  {
        i = 10;            /* … */;
        /* … */        }
    }
    

    在此模型中,包含显式初始化器的声明(即使只是默认值)即被视为该变量的定义实例。


    临时对象定义(Tentative Object Definitions)

    标准 C 引入了“临时对象定义”的概念。即一个声明是否是定义,取决于它后续是否有其他定义。例如:

    /* 临时定义,外部链接 */
    int ei1;
    
    /* 定义,外部链接 */
    int ei1 = 10;
    
    /* 临时定义,内部链接 */
    static int si1;
    
    /* 定义,内部链接 */
    static int si1 = 20;
    

    在上面的示例中,对 ei1si1 的首次引用为临时定义。如果后续没有使用初始化器再次声明相同的标识符,这些临时定义将被视为真正的定义。但如上所示,它们后面都有带初始化器的声明,因此被当作普通声明处理。这一机制的目的是为了支持两个互相引用的变量能够相互初始化指向对方。