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并发编程实战》第十五章 原子变量与非堵塞同步机制 读书笔记
一.锁的劣势 锁定后假设未释放.再次请求锁时会造成堵塞.多线程调度通常遇到堵塞会进行上下文切换,造成很多其它的开销. 在挂起与恢复线程等过程中存在着非常大的开销,而且通常存在着较长时间的中断. 锁可能 ...
随机推荐
- SpringCloud(十) - Docker
1.Docker安装 1.1 卸载旧版本(否者会安装出错) sudo yum remove docker \ docker-client \ docker-client-latest \ docker ...
- Linux网络通信(线程池和线程池版本的服务器代码)
线程池 介绍 线程池: 一种线程使用模式.线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务.这避免了在处理短时间任务时创建与销毁线程的 ...
- Linux 挂载Windows共享文件夹和NAS存储
summary: [Linux 挂载共享存储] 概述 将Windows共享文件夹和NAS存储挂载至Linux. Linux系统环境:CentOS 挂载共享存储 查看外部主机共享了哪些目录 smbcli ...
- 渗透测试中遇到的Adminer任意文件读取漏洞
渗透测试中遇到的Adminer任意文件读取漏洞 免责声明: 软件简介 漏洞原理 漏洞复现 字典脚本 直接输入文件读取脚本 直接输入文件绝对路径读取脚本使用方法 字典脚本使用方法 免责声明: 免责声明: ...
- Go语言核心36讲39
在上一篇文章中,我介绍了Go语言与Unicode编码规范.UTF-8编码格式的渊源及运用. Go语言不但拥有可以独立代表Unicode字符的类型rune,而且还有可以对字符串值进行Unicode字符拆 ...
- Go语言核心36讲29
在上篇文章中,我们主要说的是互斥锁,今天我和你来聊一聊条件变量(conditional variable). 前导内容:条件变量与互斥锁 我们常常会把条件变量这个同步工具拿来与互斥锁一起讨论.实际上, ...
- 基于echarts的带流动光效的折线图-lowline-for-echarts使用记录
起源 在技术群看到有人问这个react插件,带着好奇心看了一下. 标题:基于echarts的带流动光效的折线图 - 掘金 网址:https://juejin.cn/post/7090566240233 ...
- C温故补缺(十六):未定义行为
未定义行为 在计算机程序设计中,未定义行为是指执行某种计算机代码 所产生的结果,这种代码在当前程序状态下的行为在其所使用的语言标准中没有规定. 以C语言为例,未定义行为指C语言标准未作规定的行为,同时 ...
- Vue2基本组件间通信
Vue2组件通信的基础方式 自己的理解:组件化通信,无非就是数据你传我,我传你,两个组件的相互交流,方法很多,下方有图示(此篇建议小白阅读,大神的话也不会看,哈哈哈哈!仅供参考,有不同的意见可以一起交 ...
- easui datagrid 行获取后台sql所有数据:支持行chockbox多选,输出选中行任意属性;支持点击表中属性实现跳转;支持分页。
easyUI datagrid 代码: <table id="tabgrid20170726191838251403" class="easyui-datagrid ...