C 中的关键字“static”有两个根本不同的含义。
限制范围
在这种情况下,“静态”与“外部”配对以控制变量或函数名称的范围。静态导致变量或函数名称仅在单个编译单元中可用,并且仅可用于编译单元文本中声明/定义之后存在的代码。
只有当且仅当您的项目中有多个编译单元时,此限制本身才真正有意义。如果您只有一个编译单元,那么它仍然可以做一些事情,但这些效果大多是没有意义的(除非您喜欢深入研究目标文件以读取编译器生成的内容。)
如前所述,此上下文中的此关键字与关键字“extern”配对,后者的作用相反——通过使变量或函数名称可与其他编译单元中的相同名称链接。因此,您可以将“静态”视为要求在当前编译单元中找到变量或名称,而“外部”允许交叉编译单元链接。
静态寿命
静态生命周期意味着变量在程序的整个持续时间内都存在(不管有多长)。当您使用“静态”在函数中声明/定义变量时,这意味着该变量是在第一次使用之前的某个时间创建的(这意味着,每次我经历它时,变量都是在 main() 开始之前创建的)并且之后不会被销毁。即使函数执行完成并返回给调用者,也不会。就像在函数外部声明的静态生命周期变量一样,它们在同一时刻被初始化——在 main() 开始之前——初始化为语义零(如果没有提供初始化)或指定的显式值(如果给定)。
这与 'auto' 类型的函数变量不同,后者在每次进入函数时创建新的(或者,好像是新的),然后在函数退出时被销毁(或者,好像它们被销毁)。
与将“静态”应用于函数外部的变量定义的影响不同,后者直接影响其范围,将函数变量(显然在函数体内)声明为“静态”对其范围没有影响。范围取决于它是在函数体中定义的事实。在函数中定义的静态生命周期变量与在函数体中定义的其他“自动”变量具有相同的范围——函数范围。
概括
因此,“静态”关键字具有不同的上下文,相当于“非常不同的含义”。像这样以两种方式使用它的原因是为了避免使用另一个关键字。(对此进行了长时间的讨论。)人们认为程序员可以容忍使用语言,并且避免在语言中使用另一个关键字的价值更重要(否则比争论。)
(在函数之外声明的所有变量都具有静态生命周期,并且不需要关键字“静态”来实现这一点。因此,这种释放了在那里使用的关键字来表示完全不同的含义:“仅在单个编译中可见单位。'这是一种黑客行为。)
具体说明
静态易失无符号字符 PORTB @ 0x06;
此处的“静态”一词应解释为意味着链接器不会尝试匹配可能在多个编译单元中找到的多次出现的PORTB(假设您的代码有多个)。
它使用一种特殊的(不可移植的)语法来指定 PORTB 的“位置”(或标签的数值,通常是地址)。所以链接器得到了地址,不需要为它找到一个。如果你有两个使用这条线的编译单元,无论如何它们都会指向同一个地方。因此,这里没有必要将其标记为“外部”。
如果他们使用'extern'可能会造成问题。然后,链接器将能够查看(并尝试匹配)在多个编译中找到的对PORTB的多个引用。如果他们都指定了这样的地址,并且由于某种原因地址不一样[错误?],那么它应该做什么?投诉?要么?(从技术上讲,使用“extern”的经验法则是只有一个编译单元会指定该值,而其他编译单元不应该。)
将其标记为“静态”更容易,避免让链接器担心冲突,并且只需将不匹配地址的任何错误归咎于将地址更改为不应该的地址的人。
无论哪种方式,变量都被视为具有“静态生命周期”。(和“易变的”。)
声明不是定义,但所有定义都是声明
在 C 中,定义创建了一个对象。它也声明了它。但是声明通常不会(参见下面的项目符号)创建对象。
以下是定义和声明:
static int a;
static int a = 7;
extern int b = 5;
extern int f() { return 10; }
以下不是定义,而只是声明:
extern int b;
extern int f();
请注意,声明不会创建实际对象。它们仅声明有关它的详细信息,然后编译器可以使用这些详细信息来帮助生成正确的代码并在适当的情况下提供警告和错误消息。
上面,我建议说“通常”。在某些情况下,一个声明可以创建一个对象,因此被链接器提升为一个定义(从不被编译器提升)。所以即使在这种罕见的情况下,C 编译器仍然认为该声明只是一个声明。链接器阶段对某些声明进行了必要的提升。请牢记这一点。
在上面的例子中,如果只有“extern int b;”的声明?在所有链接的编译单元中,链接器负责创建定义。请注意,这是一个链接时间事件。编译器在编译期间完全不知道。它只能在链接时确定,如果这种类型的声明最有可能被提升。
编译器知道“static int a;” 链接器不能在链接时提升,所以这实际上是在 compile-time的定义。