文章目录
- 前提
- 正文
- 多重定义
- extern关键字
- 使用static
- static 全局变量(在.cpp文件中定义)
- static变量存放在哪里
- static变量可不可以放在.h文件中
- static 函数
- static局部变量
- static 成员变量
- static 成员函数
- 总结
- 参考链接
前提
好吧,八股,我又回来了。这次想探究下static
关键字,因为用到过,同时对下面的有些问题,还不是很清楚:
- static变量都有什么
- static关键字的作用
正文
在开始研究static
之前,我想先引导几个问题,这几个问题对接下来理解static
有一定的作用。
多重定义
有test.h, test.cpp, main.cpp
三个文件如下:
/*=====test.h=====*/
#ifndef TEST_H_
#define TEST_H_
int a = 5; /*=======这是重点======*/
void print();
#endif
/*=====test.cpp=====*/
#include"test.h"
#include<iostream>
void print()
{
std::cout<<"hello, world\n";
}
/*=====main.cpp=====*/
#include <iostream>
#include "test.h"
int main()
{
print();
std::cout<<"a: "<<a<<std::endl;
}
程序说明:在test.h
定义了一个int
的变量a
,我们想在main.cpp
中输出这个a
,使用gcc
进行编译。
运行结果:编译出错,出错信息如下:
可以看到是链接期间的错误,多重定义。
我们分析下为什么会出现这个错误。
- 首先明白程序编译的过程。 将源代码编译为可执行文件有四个阶段:预处理——>编译——>汇编——>链接。预处理大概做的是:
define
定义的替换,include
文件的拷贝(比如说:main.cpp
中include了test.h
,编译器会将test.h
的内容拷贝到main.cpp
中)等等;编译大概做的是:将C++代码编译为汇编代码;汇编大概做的是:将汇编代码编译为机器码;链接大概做的是:将所有cpp文件编译的机器码合并为一个可执行文件。大概来说就是这样,详细的大家可以自行百度。 - 这里我们先关注预处理阶段。它会将
test.h
的内容拷贝到main.cpp
中,那么此时相当于已经在main.cpp
中有了int a = 5
这个定义,有了a
这个变量(如果没有a
这个变量,编译器会直接报错找不到a
)。然后对于test.cpp
,它也include了test.h
,所以test.cpp
中也有了int a = 5
这个定义,有了a
这个变量。 - 接下来两个cpp分别进行编译,汇编。
- 到了链接阶段(上面的错误就发生在链接阶段),它将
main.cpp
的机器码和test.cpp
的机器码合并到一起,但main
中a
的定义,test
中有a
的定义,所以就会出现多重定义的这个错误。
举这个例子是想给大家说明程序编译的过程。接下来我们使用extern
关键字改写这个程序。
extern关键字
/*=====test.h=====*/
#ifndef TEST_H_
#define TEST_H_
void print();
#endif
/*=====test.cpp=====*/
#include"test.h"
#include<iostream>
int a = 5;
void print()
{
a = a+1;
std::cout<<"a: "<<a<<std::endl;
}
/*=====main.cpp=====*/
#include <iostream>
#include "test.h"
extern int a;
int main()
{
a = a+1;
std::cout<<"a: "<<a<<std::endl;
print();
}
程序说明:在test.cpp
中定义一个全局变量a
,然后在main.cpp
中想使用这个变量a
,这里我们借助了extern
这个关键字,表明:这只是a
的声明,它在其它地方定义(在test.cpp
中定义)。
运行结果:如下所示。
按照上面的分析过程,自已分析下。
使用static
我们见到的static大概有以下几类:
- static 全局变量
- static 局部变量
- static 成员变量
- static 函数
- static 成员函数
static 全局变量(在.cpp文件中定义)
改写代码如下:
/*=====test.h=====*/
#ifndef TEST_H_
#define TEST_H_
void print();
#endif
/*=====test.cpp=====*/
#include"test.h"
#include<iostream>
static int a = 5;
void print()
{
a = a+1;
std::cout<<"a: "<<a<<std::endl;
}
/*=====main.cpp=====*/
#include <iostream>
#include "test.h"
// extern int a; # 重点关注
int main()
{
print();
std::cout<<a<<std::endl;
}
程序说明:在test.cpp
中定义了一个static
变量a
,然后print()
函数使用了这个变量,最后在main.cpp
中调用print()
函数。
运行结果:
这里重点关注main.cpp
中注释的这行代码:extern int a
。从上面可知,extern
的作用是告诉编译器我的这个a
只是声明,你链接的时候在其它地方去找。
而一但我们使用static
来定义这个变量时,该变量就只在test.cpp
中可见(也就是对其它的cpp文件隐藏),这时如果我们在main.cpp
中使用extern
,编译器还是找不到变量a
的定义。运行结果如下:
所以这就是static
的第一个作用:隐藏。static
声明的全局变量只在该文件内可见,对其它文件是不可见的。
隐藏这个特点会带来什么好处呢?
- 利用这一特点可以在不同的文件中定义同名变量,而不必担心命名冲突。
明白这一点后,我们在来看一些小问题:
static变量存放在哪里
先来看看现代操作系统中一个进程的内存空间布局:
Text section: 存放二进制指令。
Data section: 存放非0初始化的静态数据和全局变量。
BSS (Block Started by Symbol): 存放0初始化的静态数据和全局变量,程序中没有初始化的静态数据会被初始为0并存放到这里。
Heap: 存放动态分配的数据。
Stack: 存放局部变量,函数参数,指针等。
来吧,让我们继续。C++的内存分布(有的人说C++没有标准的内存分布)和上面的基本一致,只不过显式指出了有一个**只读数据区(.rodata)**用来存放全局常量数据(const),如下:
回归正题,我们采用一些方法来确认static
全局变量存放在**.bss section**(未初始化)和**.data section**(初始化)。
先来确定初始化的static
变量存放在.data
中。
有以下代码:
static int variable_a=10; // 全局static变量,已初始化为10
int main()
{
}
这里要借助objdump这个命令。objdump 是一个功能强大的命令行工具,用于显示二进制文件(如可执行文件、目标文件和共享库)的各种信息。它是GNU Binutils软件包的一部分,广泛用于调试、分析和逆向工程。通过 objdump,用户可以查看文件头、段表、符号表、反汇编代码等详细信息。
编译程序,然后使用objdump -t
显示符号表,如下:
可以看到初始化的static
变量存放在.data
中。
再来确定未初始化的static
变量存放在.bss
中。修改代码如下:
#include<stdio.h>
static int variable_a;
int main()
{
printf("variable_a: %d\n", variable_a); // 验证未初始化的static变量会被编译器初始化为0,全局变量也一样
}
结果如下:
然后使用objdump
查看符号表:
可以看到未初始化的static
变量存放在.bss
中。
再来确定初始化为0的static
变量也存放在.bss
中。
修改代码如下:
#include<stdio.h>
static int variable_a=0;
int main()
{
printf("variable_a: %d\n", variable_a); // 验证未初始化的static变量会被编译器初始化为0
}
如下:
可以看到初始化为0的static
变量存放在.bss
中。
static变量可不可以放在.h文件中
代码如下 :
/*=====test.h=====*/
#ifndef TEST_H_
#define TEST_H_
static int variable_a = 5; // 重点关注:将static变量定义在.h中
void func();
#endif
/*=====test.cpp=====*/
#include"test.h"
#include<iostream>
void func()
{
variable_a += 5; // // test.cpp 中的static int variable_a 加2
std::cout<<"test.cpp: variable_a = " << variable_a <<std::endl;
}
/*=====main.cpp=====*/
#include<iostream>
#include"test.h"
int main()
{
variable_a += 2; // main.cpp 中的static int variable_a 加2
std::cout << "main.cpp: variable_a = " << variable_a << std::endl;
func();
}
程序说明:将static
变量定义在test.h
中,然后在main.cpp
中包含test.h
。
运行结果:
结果说明了:main.cpp
中的variable_a
和test.h
中的variable_a
是两个独立的变量。因为它们都是static
变量,而static
变量有隐藏的特性,所以它们互相对对方隐藏。
static 函数
static
函数和static
全局变量一样,都有隐藏的作用,所以允许同名函数。代码如下:
/*=====test.h=====*/
#ifndef TEST_H_
#define TEST_H_
static void print();
void use_print();
#endif
/*=====test.cpp=====*/
#include"test.h"
#include<iostream>
void print()
{
std::cout<<"hello static, test.cpp"<<std::endl;
}
void use_print()
{
print();
}
/*=====main.cpp=====*/
#include<iostream>
#include"test.h"
void print()
{
std::cout<<"hello static, main.cpp"<<std::endl;
}
int main()
{
print();
use_print();
}
程序说明:在test.cpp
中定义了一个static
函数print()
,同时在main.cpp
中定义了一个同名函数print()
。按照我们的分析,static
函数只在test.cpp
中可见,所以使用main.cpp
包含test.h
后,main.cpp
是看不见test.h
中的print()
函数的。可以编译运行,结果如下 :
static局部变量
static
修饰局部变量时,使得该变量不会因为函数运行结束而丢失,使其生命周期为整个源程序。把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。
代码如下:
/*==========main.cpp==========*/
#include<iostream>
int fun()
{
static int variable_a = 10; // //在第一次进入这个函数的时候,variable_a被初始化为10。接着每次调用fun(),variable自加1;在static发明前,要达到同样的功能,则只能使用全局变量:
variable_a++;
return variable_a;
}
int main()
{
for(int i=0; i<10; i++)
{
std::cout <<"variable_a: " << fun() << std::endl;
}
}
结果:
static 成员变量
代码如下:
/*==========main.cpp===========*/
#include<iostream>
class Base{
public:
Base() = default;
virtual ~Base() = default;
public:
static int variable; /* 类内定义 */
};
int Base::variable = 10; /* 类外初始化 */
int main()
{
std::cout<<"Base::variable = "<<Base::variable<<std::endl;
}
这段代码肯定是可以编译运行的,在这里我们要思考的问题是:为什么类的静态成员变量要类内定义,类外初始化?
有一个理论答案:**因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。**如果你在类内初始化,编译会报错,信息如下:
这里埋个坑,可不可以从代码层面分析为什么不能这么做?
static 成员函数
**static
成员函数和static
成员变量属于类,不属于类的某个对象。**所以static
成员函数只能调用static
成员函数和static
成员变量。
代码如下:
/*=========main.cpp============*/
#include<iostream>
class Base{
public:
Base() = default;
virtual ~Base() = default;
public:
static int variable_a; /* 类内定义 */
int a; /* 普通成员变量 */
void func1()
{
std::cout<<"this func1 is a member function"<<std::endl;
}
static void func()
{
a = 4; // error: invalid use of member 'Base::a' in static member function
func1(); // error: cannot call member function 'void Base::func1()' without object
std::cout<<"variable_a: " << variable_a <<std::endl;
}
};
int Base::variable_a = 10; /* 类外初始化 */
int main()
{
Base base;
base.func();
Base::func();
}
程序说明:在static
成员函数中调用普通成员函数和普通成员变量。
结果:程序编译出错,如下:
类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致它仅能访问类的静态数据和静态成员函数。
总结
关于static
关键字暂时总结这么多,当然关于static
还有更多的问题,接下来遇到再更新。
参考链接
- https://blog.csdn.net/u011718663/article/details/118218407
- https://www.cnblogs.com/honernan/p/14478366.html