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并发编程实战》第十五章 原子变量与非堵塞同步机制 读书笔记
一.锁的劣势 锁定后假设未释放.再次请求锁时会造成堵塞.多线程调度通常遇到堵塞会进行上下文切换,造成很多其它的开销. 在挂起与恢复线程等过程中存在着非常大的开销,而且通常存在着较长时间的中断. 锁可能 ...
随机推荐
- civil3d安装教程2022序列号和密钥
Civil3D2021 WIN10 64位安装步骤:1.先使用"百度网盘客户端"下载C3D21_CN_x64软件安装包到电脑磁盘里,并右击进行解压,安装前先断网,然后找到Autod ...
- 基于python的数学建模---运输问题
代码 import pulp import numpy as np from pprint import pprint def transport_problem(costs, x_max, y_ma ...
- C语言实验手册
在三位整数(100~999)中寻找符合条件的整数,并以此从小到大存到数组当中,它既是完全平方数,又是两位数字相同,例如144,676等. #include<stdio.h> #includ ...
- C++ using 编译指令与名称冲突
using 编译指令:它由名称空间名和它前面的关键字 using namespace 组成,它使名称空间中的所有名称都可用,而不需要使用作用域解析运算符.在全局声明区域中使用 using 编译指令,将 ...
- 推荐一款 .NET 编写的 嵌入式平台的开源仿真器--Renode
Renode 是一个开发框架,通过让你模拟物理硬件系统来加速物联网和嵌入式系统开发. Renode 可以模拟 Cortex-M.RISC-V 等微控制器,不仅可以模拟 CPU指令,还可以模拟外设,甚至 ...
- js 传递路径参数到后台的转码和解码
在开发中遇到前端页面需要将一个附件的路径传递后台实现业务逻辑,但不进行编码一直报404的错误,上代码. 前端编码:JavaScript函数encodeURL() 说明:1 .encodeURL函数主要 ...
- ArcEngine 序列化AO对象
ArcEngine中只要是继承了IPersistStream接口的对象均可调用ArcEngine中的类库进行序列化和反序列化.一般我们会序列化成xml格式,作为字符串存储,需要的时候,反序列化为对象. ...
- Spring Security(7)
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来- 有时某些业务或者功能,需要在用户请求到来之前就进行一些判断或执行某些动作,就像在Servlet中的FilterChain过滤器所做的那样,Spr ...
- 数电第三周周结_by_yc
主要内容:Modelsim和Quartus的使用坑点 Modelsim: 新建Project: 在每新建一个verilog文件时,均需要添加一project的独立路径,否则不同文件之间会相互影响! ...
- 移除元素-LeetCode27 双指针
力扣链接:https://leetcode.cn/problems/remove-element/ 题目 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返 ...