C++:C++全局变量:看完还不懂全局变量来捶我
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
我们知道全局变量时C++语言语法和语义中一个很重要的知识点首先它的存在意义需要从三个不同角度去理解。
- 对于程序员来说它是一个记录内容的变量variable
- 对于编译/链接器来说它是一个需要解析的符号 symbol
- 对于计算机来说它可能是具有地址的一块内存memory
其次从语法/语义上说:
从作用域上看带static关键字的全局变量范围只能限定在文件里否则会外联到整个模块和项目中。
从生存期看即使不带static关键字它的存储在静态全局区因为它是静态的并且贯穿整个程序或模块运行期间Notice : 正是跨单元访问和持续生存周期这两个特点使得全局变量往往成为一段受攻击代码的突破口这一点十分重要
从空间分配上看定义且初始化的全局变量在编译期会在数据段(.data)上分配空间定义但未初始化的全局变量相当于仅仅是声明暂存tentative definition在.bss段编译时自动清零而仅仅是声明的全局变量只能算个符号寄存在编译器的符号表内不会分配空间直到链接或者运行时再冲定向到相应的地址上。
下面我们从 两个方面介绍变量初始化问题
1全局变量初始化带来的变量重复定义
2变量初始值默认值问题
1全局变量初始化带来的重复定义问题。
t.h
#pragma once
int a;
foo.cpp
#include<iostream>
#include<string>
#include "t.h"
using namespace std;
struct
{
int b1;
} b = {4};
void foo() {
cout << "全局变量 &a: " << &a
<< " ,&b: " << &b
<< " ,sizeof b: " << sizeof(b)
<< " ,struct b.b: " << b.b1 << endl;
}
main.cpp
#include<iostream>
#include<string>
#include "foo.cpp"
#include "t.h"
using namespace std;
int c;
int main()
{
foo();
cout << "全局变量 &a: " << &a
<< " ,c: " << c << endl;
}
很显然
1由于t.h 头文件中 声明且定义 int a 默认初始值为0当 foo.cpp 和 main.cpp 同时引入 t.h头文件时当main.o 和foo.o 两个文件链接时就会发生 变量a 重复定义。
2同样由于 foo.cpp 声明并定义 void foo() 函数当main.cpp 引入了 foo.cpp 源文件在main.o和foo.o 两个文件链接时也会发生函数重复定义。
修改方案
解决变量的重定义可以引入 static 关键字这个关键字的作用就是
1全局变量范围只能限定在文件里不会外联到整个模块和项目中。
2变量只在源文件中有效。
解决函数重定义引入 inline关键字修饰函数
1inline 相当于在函数定义的地方将函数展开这样就可以避免函数符号在符号表中被多次找到。
// t.h
#pragma once
static int a;
// foo.cpp
#include<iostream>
#include<string>
#include "t.h"
using namespace std;
struct
{
int b1;
} b = {4};
inline void foo() {
cout << "global varial &a: " << &a << " ,a value: "<< a
<< " ,&b: " << &b
<< " ,sizeof b: " << sizeof(b)
<< " ,struct b.b: " << b.b1 << endl;
}
// main.cpp
#include<iostream>
#include<string>
#include "foo.cpp"
#include "t.h"
using namespace std;
int c;
int main()
{
foo();
cout << "global varial &a: " << &a << " ,a value: " << a
<< " ,c: " << c << endl;
}
// 打印结果
global varial &a: 0x407028 ,a value: 0 ,&b: 0x404004 ,sizeof b: 4 ,struct b.b: 4
global varial &a: 0x407028 ,a value: 0 ,c: 0
2局部/全局变量初始值或默认值问题
在C语言中的全局变量和静态变量都是会自动初始化为0堆和栈中的局部变量不会初始化造成拥有不可预测的值。
C++保证了所有对象与对象成员都会初始化但其中基本数据类型的初始化还需要依赖构造函数。下面我们讨论一下C++ 中成员变量的初始化规则
2.1 初始化语法
在C语言中在声明时用 = 即可完成初始化操作C++偏向于使用圆括号()来完成。
// C语言风格
int i = 3;
int arr[] = {1,2,3};
// C++风格
int i (3);
int i = int(4)
int* p = new int(3)
int* arr = new int[3] {1,2,3}
在C语言中 int a ----》表示声明了整型变量a但未初始化但是C++中总是会初始化无论是否写了圆括号或者是否写了参数列表
int basic_var // 未初始化应用 “默认初始化机制”
Cperson person // 初始化以空的参数列表调用构造函数。
可以参考C语言为什么没有默认初始化机制
2.2默认初始化机制规则 局部变量/全局变量/静态变量
定义基本数据类型变量单个值数组的同时可以指定初始值如果未指定C++会去执行默认初始化(default-initialization) 那么这个规则是怎样的了
栈中的变量和堆中的变量动态内存会保有不确定的值
全局变量和静态变量包括静态局部变量会初始化为零。
那么为啥会有这个区别了
首先未初始化的和初始化为零的静态/ 全局变量编译器是同样对待的把他们存储在 进程 BSS段(这是全零的一段内存空间)中所以他们会被 “默认初始化”为零。
#include<iostream>
#include<string>
using namespace std;
int g_var;
int* g_pointer;
static int g_static;
int main() {
int l_var;
int* l_pointer;
static int l_static;
cout << g_var << endl << g_pointer << endl << g_static << endl;
cout << l_var << endl << l_pointer << endl << l_static << endl;
};
很显然局部变量没有初始化编译器直接报错。
2.3成员变量的初始化
成员变量分为成员对象和内置类型成员其中成员对象总会被初始化而我们要做的就是在构造函数中初始化其中的内置类型成员
#include<iostream>
#include<string>
using namespace std;
class A {
public:
int v;
};
A g_var;
int main() {
A l_var;
static A l_static;
cout << g_var.v << ' ' << l_var.v << ' ' << l_static.v << endl;
return 0;
}
打印结果 0 6422280 0
从打印结果可知内置类型的成员变量的 “默认初始化”行为取决于所在对象的存储方式而存储方式对应的默认初始化规则是不变的。即全局对象变量 g_var 和局部静态变量对象 l_static存储在静态区所以对象的 内置类型也存储在静态区所以这个内置类型变量就可以默认初始化而局部变量对象 l_var 存储在 栈中所以它的 内置类型也在栈中所以它就不能默认初始化因而也就存在不确定值 。
所以
在 Effective C++ item4 中讨论了如何正确地在构造函数中初始化数据成员这里不过多讨论直接给出初始化的写法
class A{
public:
int v;
A(): v(0); // 直接在圆括号中初始化变量 v
};