C-08\变量类别和名称粉碎机制
全局变量
定义:在所有函数外部定义的变量称为全局变量,一般以g_开头,如
char g_szBuf[100]; // 全局变量g_szBuf
int main()
{
printf("%s\r\n",g_szBuf);
return 0;
}
作用范围(作用域):从声明变量的位置开始到源程序结束,即全局变量可以被在其定义之后的其他函数所共享
当扩展名为.c时全局变量不能以未知值进行初始化,不然会报错:
initializer is not a constant(初始化式不是常量)当扩展名为
.cpp时全局变量可以以未知值初始化,如函数的返回值(运行时才能确定返回值)
int GetInt()
{
return 0x87654093;
}
int g_nTest = GetInt();
int main()
{
return 0;
}
如果以函数的返回值初始化全局变量,则该函数会在main函数之前运行(这是抢在main函数之前执行的方法之一),如上面的GetInt()函数会在main函数之前运行
以常量初始化全局变量,常量在链接阶段会直接被写进文件中,因此在模块创建时就会在内存中存在这个全局变量,模块卸载才会消失,如图所示:


所以如果想修改全局变量可以直接在文件中修改
已初始化的全局变量放在已初始化数据区,未初始化的全局变量放在未初始化数据区

如果想要文件小一点,全局变量使用未初始化(或初始化为0),这样链接时基本不会增加生成的可执行文件的大小,如下图


如果全局变量初始化且不全为0,链接的时候就会在生成的可执行文件中预留足够的空间,从而使可执行文件大小变大

编译器会将所有未初始化数据归纳起来,求对齐后的总值,然后在链接(link)做可执行程序时记录下这个总值,最后运行时操作系统根据这个预设值再提供足够的空间供未初始化数据使用
void __cdecl _cinit (
void
)
{
/*
* initialize floating point package, if present
*/
#if defined (_M_MRX000) || defined (_M_ALPHA) || defined (_M_PPC)
/*
* MIPS compiler doesn't emit external reference to _fltused. Therefore,
* must always force in the floating point initialization.
*/
_fpmath();
#else /* defined (_M_MRX000) || defined (_M_ALPHA) || defined (_M_PPC) */
// 如果存在浮点数
if ( _FPinit != NULL )
(*_FPinit)(); // 初始化浮点协处理器
#endif /* defined (_M_MRX000) || defined (_M_ALPHA) || defined (_M_PPC) */
/*
* do initializations
*/
// 初始化官方对象,如 cin、cout等
_initterm( __xi_a, __xi_z );
/*
* do C++ initializations
*/
// 我们自己的全局变量在该函数进行初始化
_initterm( __xc_a, __xc_z );
}
#ifdef CRTDLL
void __cdecl _initterm (
#else /* CRTDLL */
static void __cdecl _initterm (
#endif /* CRTDLL */
_PVFV * pfbegin,
_PVFV * pfend
)
{
/*
* walk the table of function pointers from the bottom up, until
* the end is encountered. Do not skip the first entry. The initial
* value of pfbegin points to the first valid entry. Do not try to
* execute what pfend points to. Only entries before pfend are valid.
*/
while ( pfbegin < pfend )
{
/*
* if current table entry is non-NULL, call thru it.
*/
if ( *pfbegin != NULL )
(**pfbegin)();
++pfbegin;
}
}

inline修饰符
- 告诉编译器,直接把该函数的机器码插入到调用它的地方
- 不是强制性的,编译器有可能会置之不理,如:递归函数通常不会被编译成内联函数
- 会检查参数是否合法(编译器级别的检查)
extern修饰符
extern 修饰符用来告诉编译器,该函数或变量在外部
.obj文件中有定义。链接阶段会在其它文件中找,如果链接阶段在其它文件中找不到,则链接不会通过// 告诉编译器在别的文件中查找 g_szBuf 数组
// 注意 extern 后面的变量不能有实质性动作(不能赋值,不能定义)
extern char g_szBuf[]; // 错误的。 不能赋值
extern char g_szBuf[] = "hello world"
- 声明:没有额外的机器行为,只是知会一下编译器,该变量或函数存在
- 定义:要说明这个事怎么做,如定义一个函数,那就要定义函数的流程,定义函数的结构。那就要产生机器行为
extern "C" 用来标识函数或变量,使用标准的C编译器符号粉碎规则
extern 用来实现全局变量和函数的跨文件访问
Bug(vc++6.0)
下面程序会崩溃,不会显示1234。原因是编译器认为下面程序没有浮点运算,所以没有初始化浮点协处理器,而
scanf()函数中有浮点运算,所以导致了崩溃。后面高版本解决的办法是:不管程序有没有浮点运算都初始化浮点协处理器int main()
{
float m = 0.0f; // 加上这句编译器就会初始化浮点协处理器,程序可以正常运行
float f;
scanf("%f",&f);
printf("1234");
return 0;
}
下面程序会进入死循环,不会显示1234。早期版本的编译器
(VC++6.0),因为goto编译器发现return 0是永远不会抵达的代码段,因此这段代码段不会参与编译,即删除return 0(将后面代码的行数往上推,不小心多推了一下,循环多了一次),就变成了NEXT: goto NEXT;自己跳自己,就会一直循环下去int fun()
{
goto NEXT;
return 0;
NEXT:
;
} int main()
{
fun();
printf("1234");
return 0;
}
vc6.0的watch窗口使用C风格名称粉碎查找变量,导致.cpp文件中的静态局部变量不能使用watch窗口查看,解决办法将.cpp文件改为.c文件或者借用全局变量在内存窗口定位
静态变量
定义:变量前加static修饰符
作用:
相当于限制了作用域的全局变量
修饰全局变量或函数,则将该变量或函数的作用域限制在其定义的文件,其余文件无法访问,即使在其余文件中使用了
extern修饰,也无法使用这是因为编译器在编译的时候虽然依然会编译生成全局变量(在
.obj有静态全局变量),但是编译器没有为其生成外部符号的导出,所以外部访问不到。导致链接器找不到该静态全局变量符号,无法链接通过修饰局部变量,则将该变量的作用域限制在定义的函数,其余函数无法访问。
只会初始化一次,如果以常量进行初始化,则不会产生任何代码,编译链接的时候直接将常量写入文件中(已初始化数据区)
void fun(int n)
{
static int stcnTest = 0x789;
}
如果以变量进行初始化,为了实现只会初始化一次,会有一个全局的标志。早期在多线程中使用静态局部变量会导致多次初始化的问题(在全局标志修改前变量已经被多个线程初始化)。
vs2013以后的版本设置了线程安全(将该标志保证在TLS中),解决了这个问题int g_isStcnTestInit = 0;
void fun(int n)
{
if(!g_isStcnTestInit)
{
static int stcnTest = n;
g_isStcnTestInit = 1;
}
}
面向对象的萌芽期,类似于私有
私有的函数和变量使用
static限制在文件中,一人负责一个文件互不干涉
| 检查的标准程度(编译链接阶段检查) | |
|---|---|
| 全局变量 | 跨文件 |
| 静态全局变量 | 不能跨文件 |
| 静态局部变量 | 不能跨函数 |
名称粉碎机制
一般会有作用域,原先名称和层级三项信息(每个编译器的名称粉碎规则是不一样的)
extern "C":不管函数参数类型,所以也就不支持函数重载。而C++中函数的参数类型也会参与到名称粉碎,这也是其支持函数重载的原理vc++6.0集成开发环境中有一个undname工具,可以将名称粉碎后的函数名字还原成名称粉碎前函数名C:\Program Files (x86)\Microsoft Visual Studio\Common\Tools\UNDNAME.EXE
void fun()
{
// _?stcTest@?1??fun@@YAXXZ@4HA
static int stcTest = 0x3838438;
printf("%d\r\n",stcTest);
}
int main(int argc, char *argv[])
{
// _?stcTest@?1??main@@9@4HA
static int stcTest = 0x1314520;
printf("%d\r\n",stcTest);
fun();
return 0;
}
寄存器变量
- 定义时加上类型修饰
register - 只限于int型、char型和指针类型
- 如果寄存器使用饱和时,程序将寄存器变量自动转换为自动变量处理,不会通知你
- debug版本无论声明与否都不会使用寄存器变量(为了方便调试)
- release版本无论声明与否编译器都会上寄存器变量(有空闲时)
C-08\变量类别和名称粉碎机制的更多相关文章
- Revit API取得变量的内参名称
与取得元素变量的内参名称类别有个BuiltInParameter //取得内参名称 [Transaction(TransactionMode.Manual)] [Regeneration(Regene ...
- GNU C++的符号改编机制介绍(函数的名称粉碎格式解析)
转载:http://blog.csdn.net/roland_sun/article/details/43233565 众所周知,强大的C++相较于C增添了许多功能.这其中就包括类.命名空间和重载这些 ...
- Lua中如何实现类似gdb的断点调试—08支持通过包名称添加断点
在前一篇中我们支持了通过函数名称来添加断点,我们同时也提到了在Lua中一个函数的名称的并不是确定的.准确的说,Lua中的函数并没有名称,所谓名称其实是保存这个函数值的变量的名称. 于是通过函数名称添加 ...
- Python变量的本质与intern机制
变量的存储 a = 'abc' 理解:①先在内存中生成一个字符串‘abc’ ②可以把比变量名a看做一个便利贴,然后将a贴到‘abc’中 ③注意顺序,是生成‘abc’,然后再创建a指向‘abc’ ...
- Android-Java-静态成员变量&成员变量&局部变量(内存图&回收机制)
静态成员变量(回收机制) StaticDemo 和 MyDemo package android.java.oop13; class MyDemo { /** * 定义一个静态变量 */ public ...
- Java并发编程实战 第15章 原子变量和非阻塞同步机制
非阻塞的同步机制 简单的说,那就是又要实现同步,又不使用锁. 与基于锁的方案相比,非阻塞算法的实现要麻烦的多,但是它的可伸缩性和活跃性上拥有巨大的优势. 实现非阻塞算法的常见方法就是使用volatil ...
- Java基础以及变量和运算符、包机制、javadoc生成
目录 注释.标识符.关键字 注释 标识符 关键字 标识符注意点 数据类型 强类型语言 弱类型语言 Java的数据类型 基本类型(primitive type) 数值类型 boolean类型 什么是字节 ...
- Python-变量、变量作用域、垃圾回收机制原理-global nonlocal
变量实现原理决定了Python使用的垃圾回收机制为变量引用计数,当这个对象引用计数为0时候,则会自动执行__del__函数回收资源, del方法只是把变量指向的对象引用计数减一而已并删除这个变量 表达 ...
- eclipse点击一个变量使相同名称变量高亮显示的方法
preferences->java->Editor->Mark Occurences 选择最上的复选框,下面的就有很多了. 其中的Local variables就是变量的高亮显示.
- 《Java并发编程实战》第十五章 原子变量与非堵塞同步机制 读书笔记
一.锁的劣势 锁定后假设未释放.再次请求锁时会造成堵塞.多线程调度通常遇到堵塞会进行上下文切换,造成很多其它的开销. 在挂起与恢复线程等过程中存在着非常大的开销,而且通常存在着较长时间的中断. 锁可能 ...
随机推荐
- 强连通分量与tarjan算法初步运用
模板题:B3609 [图论与代数结构 701] 强连通分量 题目描述 给定一张 n 个点 m 条边的有向图,求出其所有的强连通分量. 注意,本题可能存在重边和自环. 输入格式 第一行两个正整数 n , ...
- Go语言核心36讲06
我已经为你打开了Go语言编程之门,并向你展示了"程序从初建到拆分,再到模块化"的基本演化路径. 一个编程老手让程序完成基本演化,可能也就需要几十分钟甚至十几分钟,因为他们一开始就会 ...
- 记一次spark数据倾斜实践
参考文章: 大数据项目--倾斜数据的分区优化 数据倾斜概念 什么是数据倾斜 大数据下大部分框架的处理原理都是参考mapreduce的思想:分而治之和移动计算,即提前将计算程序生成好然后发送到不同的 ...
- i春秋Zone
打开网页是个简单的表单填写, 尝试注入....没用 查看源码,没找到什么有用的信息 只有抓包了 发现一个cookie的login值为0,改为1试试 没什么特别的回显,但这应该就是登录与否的判定了,所以 ...
- C ++:树
C++:树 树的概念: 所谓"树"是输就结构的一种,树大概可以分为两大类: 有根树 和 无根树 有根树使有一个确定的根节点,反之为无根树 · 子节点:从树根开始,通过树边向下扩展的 ...
- C# Panel动态添加滚动条
/// <summary> /// panel控件的事件:在向该控件添加控件时发生 /// </summary> private void panel1_ControlAdde ...
- 3.7V升压5V,3.7V转5V电路图芯片
锂离子电池在如今是广泛应用存在我们生活中的方方面面的电子产品中.如,电子玩具,美容仪,医疗产品,智能手表,手机,笔记本,电动汽车等等非常多. 锂电池3.7V升压到5V,3.7V转5V稳压输出的电子产品 ...
- Linux 常用命令(持续更新)
Linux常用命令介绍(备查) *所有的命名都可以用 命令 --help/man 命令 查看使用说明 1.pwd 显示当前路径 2.dir 和 ls用法一样 都是列出当前路径下的文件(不包括隐藏文件) ...
- C# Log4net配置文件 总结
前言 因为项目日志太杂乱而且很大,打开一个就卡死了,何况用户电脑也扛不住日志积累,要做一个日志记录器管理日志.但对里面的配置有一些不熟悉(毕竟都是复制粘贴的),所以记录一下各个项的作用.方便后续复习. ...
- C#多线程(四)并行编程篇之结构化
前言 在前三章中我们的案例大量使用到了Thread这个类,通过其原始API,对其进行创建.启动.中断.中断.终止.取消以及异常处理,这样的写法不仅不够优雅(对接下来这篇,我称其为.NET现代化并行编程 ...