title: static
date: 2023-12-13 12:00:00
categories:
- C++
- static
tags: C++ #vs那些事
Static
在class/struct外
static
关键字用于局部变量时,会改变该变量的存储持续性和初始化行为
- 存储持续性:通常,局部变量只在其定义的代码块中存在,当代码块执行完毕后,局部变量就会被销毁。但是,如果局部变量被
static
修饰,它就会在程序的整个运行期间持续存在,即使它的定义代码块已经执行结束。这意味着,static
局部变量在函数调用之间保持其值。 - 初始化行为:
static
局部变量只会在第一次执行到它的定义时被初始化一次,之后即使多次进入定义它的代码块,也不会重新初始化。 - 默认初始化为零:如果
static
局部变量没有显式初始化,它会被自动初始化为零。 - 内部链接:
static
关键字还可以用于全局变量和函数,此时它限制了变量或函数的链接属性,使得它们只能在定义它们的文件内部访问,而不能被其他文件访问。
使用static相当于在这个文件里的声明私有变量,就像class中的private
static int s_variable = 5;//static·
定义的函数和变量只对它的声明所在的cpp文件(编译单元)是“可见
的
当从其他编译单元通过extern int s_variable
来查到变量的外部定义时,该变量不可见
在class/struct内
如果在类或结构体内定义一个静态变量,表示在这个类的所有实例中,这个变量只有一个实例,也就是这个变量的全局实例,所以在实例中引用这个变量/函数是没有意义的
本例中都为非静态变量,Entity的两个实例 e,e1 可直接访问类内变量及函数
#include <iostream>
struct Entity
{
int x, y;
void Print()
{
std::cout << x << " , " << y << std::endl;
}
};
//int Entity::x;
//int Entity::y;
int main()
{
Entity e;
e.x = 1;
e.y = 2;
Entity e1;
e1.x = 5;
e1.y = 7;
e.Print();
e1.Print();
std::cin.get();
}
运行后输出1,2 5,7
如果修改为静态变量 x,y 以及Print()
struct Entity
{
static int x, y;
static void Print()//当然静态方法无法访问非静态变量
{
std::cout << x << " , " << y << std::endl;
}
};
则e.x = 1; e.y = 2; e1.x = 5; e1.y = 7;
是无意义的,因为x,y只有一个副本(所有实例引用的x,y都指向同一个空间)
所以x,y可以理解为属于Entity的“全局变量”,int Entity::x;int Entity::y;
之后,将所有的诸如e.x e1.x
替换Entity::x
(就好像这些变量只属于Entity)
int Entity::x;
int Entity::y;
int main()
{
Entity e;
Entity::x = 1;
Entity::y = 2;
Entity e1;
Entity::x = 5;
Entity::y = 7;//将x,y修改为了5,7
Entity::Print();
Entity::Print();//此处可看出e.Print();e1.Print();是相同的
std::cin.get();
}
修改后输出5,7 5,7
这种做法方便跨类使用变量以及各实例共享变量.
localstatic
#include <iostream>
//在此处定义为全局变量则所有地方都可以访问i,在任意函数中都可以对其进行修改
//int i=0;
//暂时不懂QQQ
class Singleton
{
private:
static Singleton* s_Instance;
public:
static Singleton& Get() {
return *s_Instance;
}
};
void function()
{
//局部变量,仅作用在该函数周期,调用结束则释放
//int i = 0;
static int i = 0; //仅能在函数内被访问的静态局部变量
i++;
std::cout << i << std::endl;
}
int main()
{
function();
function();
function();
function();
std::cin.get();
}
单例模式Singleton详见
使用场景
2024-06-11 19:18💦 补充
由static的性质可以做的:
for(int i = 0;i<infds;i++)
{
//如果发生事件的是listensock,表示有新的连接
if(evs[i].data.fd==listensock)
{
static int i =0;
printf("这是第%d个连接。\n",++i);
//处理连接的代码
}
//非监听事件的处理
}
这段代码中使用 static
关键字的原因是为了跟踪和记录发生的连接次数。在 for
循环中,每次循环迭代都会检查是否有新的连接(即 evs[i].data.fd
是否等于 listensock
)。如果有新的连接,代码块内的 static int i = 0;
语句会执行。
由于 static
变量的特性,这个名为 i
的 static
变量只会在第一次循环迭代时被初始化为0,并且它的值会在函数调用之间持续存在123。这意味着,即使 for
循环多次执行,static
变量 i
的值也不会重置,而是会保留上一次增加后的值。
因此,每次检测到新连接时,static int i
的值会增加1,并且通过 printf
函数打印出来,显示这是第几个连接。这样,即使 for
循环的局部变量 i
在每次迭代时都会改变,static
变量 i
仍然能够正确地计数连接的总数。
如果不使用 static
变量,而是直接使用 printf
,那么我们将无法跟踪连接的总数,因为没有变量来累计这个值。每次循环时局部变量 i
的值都是基于循环迭代的,与连接次数无关。
这里的 static
变量 i
与循环控制变量 i
是两个不同的变量,尽管它们有相同的名字。循环控制变量 i
的作用域是整个 for
循环,而 static
变量 i
的作用域是它被声明的代码块内。使用 static
变量是一种常见的技术,用于在多次函数调用或循环迭代中保持变量值的持久性。
面试题
面试题:C/C++中static底层怎么做到让其他文件无法访问的?
在C和C++中,static关键字有多种用途,其中之一就是控制作用域和链接性,以限制变量或函数的可见性。
当在一个源文件中声明一个变量或函数为static时,它改变了该实体的链接属性,使其仅在当前的编译单元(即源文件)内可见和可访问。这有效地隐藏了static变量或函数,防止其他源文件中的代码直接访问它们。
链接,符号
在了解下面原理之前需要知道一些东西,程序的链接过程:
.c
文件处理-预处理-编译-汇编-链接(符号解析+重定位(.o可重定位文件))
重定位就是按调用顺序来指定被链接的模块顺序,包括:合并相关.o文件,确定每个符号地址,在指令中填入新地址。这里不过多提及,重点讲述符号解析。
符号解析(SR)又叫符号绑定,程序中定义与引用的符号(包括变量和函数),将符号保存在符号表.symtab节(结构数组)中,函数名在.text节,变量名在.data/.bss节(已初始化.data/未初始化)。
符号的定义有三种:
- Global symbols(模块内部定义的全局符号) eg:非static函数和非static的全局变量。
- External symbols(外部定义的全局符号) eg:其他模块定义,被当前模块引用的全局符号
- Local symbols(本模块的局部符号)eg:模块m中定义的static修饰的函数和全局变量
注意,局部变量分配在栈中,不会在程序外被引用,因此局部变量不是符号定义。
static变量和函数的底层实现
在底层,static关键字的工作原理与编译器和链接器的处理方式紧密相关。以下是一些关键点:
- 符号本地化:
当你在一个源文件中声明一个static变量或函数时(被声明为Local symbols),编译器生成的符号(即变量或函数的标识符)具有内部链接属性,这意味着它只在当前的编译单元中可见,
在链接阶段,链接器不会将这些static符号导出到其他模块,也不会将其他模块中的同名符号导入进来。
- 符号解析:
链接器在链接阶段会解析符号引用。对于static符号,链接器只会查找当前模块内的定义,而不会在其他模块中寻找。
如果其他源文件尝试引用一个标记为static的符号,链接器会报告未解析的符号错误,因为那个符号不会在其他模块的符号表中出现。
- 符号唯一性:
使用static可以避免符号冲突。即使多个源文件中有同名的static变量或函数,每个变量或函数只在各自的编译单元中存在,不会引起命名冲突。
- 存储位置:
static变量存储在全局数据段中,但其作用域仅限于声明它的文件,这意味着它在程序的整个生命周期中都存在,但只能被该文件中的代码访问。
static函数则存储在代码段中,同样,其作用域仅限于声明它的文件。
更加详细:
静态全局变量的符号不会被导出到符号表中,主要原因是为了限制其作用域,从而避免其他编译单元(即其他源文件)引用或访问该变量。这是由
static
关键字的设计目的决定的,即在编译单元内部隐藏符号,实现一种内部链接性(internal linkage)。下面是更详细的解释:1. 符号表和链接
- 在编译和链接过程中,每个编译单元(通常是每个源文件)都会生成自己的符号表,其中包含了所有全局变量、函数等符号的名称和相关信息。
- 如果一个符号(例如全局变量或函数)有外部链接性(external linkage),它的符号信息会被放入符号表中,供其他编译单元在链接时访问。
- 链接器通过符号表来将多个编译单元中的符号相互关联起来,最终生成可执行文件或库。
2.
static
限制符号的可见性
- 当一个全局变量使用
static
关键字修饰时,它的作用域被限制在当前编译单元中,即该变量只能在定义它的文件内使用,其他文件无法访问它。- 为了实现这一点,编译器会将该变量标记为内部链接性(internal linkage)。对于内部链接的符号,编译器不会导出它到符号表中,这意味着链接器在其他编译单元中无法看到或引用该符号。
3. 底层原理
- 在汇编层级,符号表中存在着两种符号:
- 外部符号:具有外部链接性的符号,其他文件可以通过
extern
访问它们。- 内部符号:具有内部链接性的符号,仅限于当前编译单元使用。
- 当
static
修饰全局变量时,编译器将该变量的符号标记为局部符号(local symbol)。局部符号在最终的符号表中是不可见的,不会导出给其他编译单元。4. 避免符号冲突
- 另一个重要原因是避免符号冲突。假设在不同的文件中有多个全局变量名相同,如果这些变量都没有被
static
修饰,它们都会被导出到符号表中,从而在链接时产生冲突。- 使用
static
修饰这些变量后,即使它们名称相同,编译器也会将它们视为不同编译单元内的独立符号,因为它们只在各自文件内可见,从而避免了链接时的冲突。结论
静态全局变量的符号不会被导出到符号表中,因为:
static
关键字为该变量指定了内部链接性,它只能在当前编译单元中访问,其他文件无法引用。- 这样可以避免符号冲突,提升封装性和代码模块化,使变量的作用范围更明确,防止不必要的外部访问。
相关建议
a. 是否需要了解如何使用
nm
工具查看符号表中的符号?
b. 你想探讨如何在多文件项目中避免符号冲突吗?