static关键字,使用以及原理


title: static
date: 2023-12-13 12:00:00
categories:

  • C++
  • static
    tags: C++ #vs那些事

Static

在class/struct外

static 关键字用于局部变量时,会改变该变量的存储持续性和初始化行为

  1. 存储持续性:通常,局部变量只在其定义的代码块中存在,当代码块执行完毕后,局部变量就会被销毁。但是,如果局部变量被 static 修饰,它就会在程序的整个运行期间持续存在,即使它的定义代码块已经执行结束。这意味着,static 局部变量在函数调用之间保持其值。
  2. 初始化行为static 局部变量只会在第一次执行到它的定义时被初始化一次,之后即使多次进入定义它的代码块,也不会重新初始化。
  3. 默认初始化为零:如果 static 局部变量没有显式初始化,它会被自动初始化为零。
  4. 内部链接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 变量的特性,这个名为 istatic 变量只会在第一次循环迭代时被初始化为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关键字的工作原理与编译器和链接器的处理方式紧密相关。以下是一些关键点:

  1. 符号本地化:

当你在一个源文件中声明一个static变量或函数时(被声明为Local symbols),编译器生成的符号(即变量或函数的标识符)具有内部链接属性,这意味着它只在当前的编译单元中可见,

在链接阶段,链接器不会将这些static符号导出到其他模块,也不会将其他模块中的同名符号导入进来。

  1. 符号解析:

链接器在链接阶段会解析符号引用。对于static符号,链接器只会查找当前模块内的定义,而不会在其他模块中寻找。

如果其他源文件尝试引用一个标记为static的符号,链接器会报告未解析的符号错误,因为那个符号不会在其他模块的符号表中出现。

  1. 符号唯一性:

使用static可以避免符号冲突。即使多个源文件中有同名的static变量或函数,每个变量或函数只在各自的编译单元中存在,不会引起命名冲突。

  1. 存储位置:

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. 你想探讨如何在多文件项目中避免符号冲突吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值