文档章节

VC++全局变量初始化

h
 hanford
发布于 2016/11/04 19:20
字数 1657
阅读 15
收藏 0

1说明    2

1.1 程序启动    2

1.2 强符号、弱符号    2

1.3 动态初始化顺序    3

1.4 exe调用dll    4

1.5 禁用动态初始化    4

1.6 应用实例    5

 

1说明

1.1 程序启动

参考下面的C++代码:

int GetC() { return 2;};

int a;

int b = 1;

int c = GetC();

int main()

{

return a+b+c;

} 

程序载入内存,全局变量abc就完成了静态初始化(static initialization),此时abc的数值分别为010

系统会调用入口函数mainCRTStartup,后者会调用_initterm(__xc_a,__xc_z);执行_initterm函数时会调用GetC函数,完成全局变量c的动态初始化(dynamic initialization)。此时abc的数值分别为012

mainCRTStartup会接着调用函数main,至此完成程序的启动工作。

注意:C语言里的全局变量只能静态初始化;C++语言里的全局变量才支持动态初始化。

1.2 强符号、弱符号

上面示例代码中的全局变量a没有赋给初始值,它就是弱符号。弱符号全局变量会被编译器初始化为零。

上面示例代码中的全局变量bc赋予了初始值,它们就是强符号。

弱符号可能会被合并。如:1.cpp2.cpp里均有弱符号a,那么连接时它们将被当做一个全局变量。又如:1.cpp2.cpp里均有弱符号a3.cpp里有强符号a,那么连接时以强符号为准。

强符号是不会被合并的。如:1.cpp2.cpp里均有强符号a,那么连接时就会出错。

建议:尽量使用强符号,否则可能会产生难以察觉的错误。

1.3 动态初始化顺序

C++代码里,可使用#pragma init_seg来调整动态初始化的顺序。其顺序一共分为52级,如下表所示:

#pragma init_seg(".CRT$XIA")

#pragma init_seg(".CRT$XIB")

... ... ...

#pragma init_seg(".CRT$XIZ")

#pragma init_seg(".CRT$XCA")

#pragma init_seg(".CRT$XCB")

#pragma init_seg(".CRT$XCC") #pragma init_seg(compiler)

... ... ...

#pragma init_seg(".CRT$XCL") #pragma init_seg(lib)

... ... ...

#pragma init_seg(".CRT$XCU") #pragma init_seg(user)

... ... ...

#pragma init_seg(".CRT$XCZ") 

上表中,越靠上的段内全局变量越先被动态初始化。如下面的两个cpp文件内容:

#pragma init_seg(".CRT$XCC")

int C1 = GetC1();

int C2 = GetC2(); 

#pragma init_seg(".CRT$XCL")

int L1 = GetL1();

int L2 = GetL2();

全局变量C1C2在段.CRT$XCC里,L1L2在段.CRT$XCL里,所以C1C2肯定比L1L2优先完成动态初始化。

同一段内全局变量的初始化顺序完全无法预料。如:上面C1C2的动态初始化顺序是无法预料的。

全局变量默认在段.CRT$XCU内,C++库的全局变量在段.CRT$XCC内,MFC库的全局变量在段.CRT$XCL内。所以,编写一个MFC程序并且静态连接MFC库时,C++库的全局变量首先被动态初始化,然后MFC库的全局变量被动态初始化,最后是MFC程序里的全局变量被动态初始化。这样的动态初始化顺序非常重要,因为MFC程序里的全局变量有可能会调用C++库或MFC库里的函数或变量。不调整好顺序就有可能出错。如下面的代码:

#include <stdio.h>

#include <string>

#pragma init_seg(".CRT$XCA")

std::string s = "123";

void main()

{

puts(s.c_str());

}

如果上述代码静态连接C运行时库,那么在C++库内的全局变量被初始化前,全局变量s就被动态初始化了。结果就是s动态初始化失败,其成员变量全部为零(静态初始化的结果)。

1.4 exe调用dll

上一节的例子,如果动态连接C运行时库(msvcrt.dll),那么全局变量s就能正常动态初始化。其初始化步骤为:

1、初始化dll内的全局变量

2、调用dll内的DllMain(...,DLL_PROCESS_ATTACH,...)

3、初始化exe内的全局变量

4、调用 exe 内的main WinMain

5、从 exe 内的main WinMain 返回

6、销毁exe内的全局变量

7、调用 dll 内的DllMain(...,DLL_PROCESS_DETACH,...)

8、销毁dll内的全局变量

1步中,msvcrt.dll内部完成了C++库全局变量的初始化工作;第3步动态初始化exe里的全局变量s时,就不会有问题了。

1.5 禁用动态初始化

全局变量的动态初始化是入口函数mainCRTStartup 调用_initterm函数产生的结果。如果自行指定入口函数,就不再会有动态初始化了。如下面的代码:

#include <windows.h>

int GetA() { return 1; }

int a = GetA();

#pragma comment(linker,"/entry:MyEntry") //修改入口函数为 MyEntry

void main(){}

void MyEntry()

{

MessageBox(NULL,a ? TEXT("1") : TEXT("0"),TEXT(""),MB_OK);

}

全局变量a将无法完成动态初始化,其值为零。

注意:自行指定入口函数,静态连接C函数库时无法动态初始化C函数库里的全局变量,将导致C函数库里的函数无法使用。

1.6 应用实例

请参考如下几段MFC代码:

UINT GetMsgId()

{

static UINT uID = WM_USER + 100;

return uID++;

}

//串口通讯模块

//收到串口数据后PostMessage(hWndMain,WM_SERIAL_RECV)

UINT WM_SERIAL_RECV = GetMsgId(); 

//网络通讯模块

//收到网络数据后PostMessage(hWndMain,WM_SOCKET_RECV)

UINT WM_SOCKET_RECV = GetMsgId(); 

//主窗口 CDlgMain

BEGIN_MESSAGE_MAP(CDlgMain, CDialog)

ON_MESSAGE(WM_SERIAL_RECV,OnSerialRecv)

ON_MESSAGE(WM_SOCKET_RECV,OnSocketRecv)

END_MESSAGE_MAP() 

串口通讯模块收到串口数据后,给主窗口寄送WM_SERIAL_RECV消息;网络通讯模块收到网络数据后,给主窗口寄送WM_SOCKET_RECV消息。为了防止WM_SERIAL_RECVWM_SOCKET_RECV重复,特使用GetMsgId函数对它们进行动态初始化。

通过主窗口CDlgMain的消息映射表可知:收到WM_SERIAL_RECV消息,将调用函数OnSerialRecv进行处理;收到WM_SOCKET_RECV消息,将调用OnSocketRecv进行处理。

VC++6.0里展开BEGIN_MESSAGE_MAPON_MESSAGE就是如下代码:

const AFX_MSGMAP_ENTRY CDlgMain::_messageEntries[] =

{

{ WM_SERIAL_RECV, 0, 0, 0, AfxSig_lwl, &OnSerialRecv },

{ WM_SOCKET_RECV, 0, 0, 0, AfxSig_lwl, &OnSocketRecv },

}

全局变量WM_SERIAL_RECVWM_SOCKET_RECVCDlgMain::_messageEntries均在段.CRT$XCU里,所以它们的初始化顺序是不可预知的。

如果CDlgMain::_messageEntries先于WM_SERIAL_RECVWM_SOCKET_RECV动态初始化,那么很不幸,此时的WM_SERIAL_RECVWM_SOCKET_RECV均为零,所以CDlgMain将无法处理WM_SERIAL_RECVWM_SOCKET_RECV消息。

为了让WM_SERIAL_RECVWM_SOCKET_RECV先于CDlgMain::_messageEntries动态初始化,可这样修改代码:

//串口通讯模块

//收到串口数据后PostMessage(hWndMain,WM_SERIAL_RECV)

#pragma init_seg(lib)

UINT WM_SERIAL_RECV = GetMsgId(); 

//网络通讯模块

//收到网络数据后PostMessage(hWndMain,WM_SOCKET_RECV)

#pragma init_seg(lib)

UINT WM_SOCKET_RECV = GetMsgId(); 

现在WM_SERIAL_RECVWM_SOCKET_RECV在段.CRT$XCL里,而CDlgMain::_messageEntries在段.CRT$XCU里。这样就能保证CDlgMain::_messageEntries动态初始化前WM_SERIAL_RECVWM_SOCKET_RECV已经动态初始化完毕。CDlgMain也就能够正常处理WM_SERIAL_RECVWM_SOCKET_RECV消息了。

© 著作权归作者所有

h
粉丝 0
博文 12
码字总数 20482
作品 0
西安
私信 提问
C语言中未初始化变量的值

我们在刚学C编程的时候经常碰到“烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫”,这是为什么呢? 主函数外面(全局)和里面(局部)个定义一个int类型数值。然后没有赋值,直...

ChuckleLiu
2016/04/12
104
0
VC中使用GDI+实现为按钮加载Png图片

VC中使用GDI+实现为按钮加载Png图片 作者:flyfish 2010-3-15 一、在stdafx.h文件中添加头文件,命名空间,库的引用, #include usingnamespaceGdiplus; #pragmacomment( lib, "gdiplus.lib"......

冯京宝
2010/10/31
0
0
c执行文件内存布局

c语言在嵌入式、操作系统、图像处理方面应用广泛,是一种比较底层的语言。本文主要介绍c语言的内存分配,进程在内存中的布局。 环境: Linux zhuzhu 4.2.0-27-generic #32~14.04.1-Ubuntu S...

菏泽小朱
2016/10/07
0
0
C++ 对象构造, 拷贝, 赋值和隐式类型转换总结

原文:C++ 对象构造, 拷贝, 赋值和隐式类型转换总结 作者:Breaker C++ 中对象创建(构造)、拷贝、赋值、隐式类型转换的阶段性总结 关心效率和拷贝开销,写在代码注释中 关键字:对象创建、...

晨曦之光
2012/05/23
1K
0
LNK2005错误——重复定义错误

造成LNK2005错误主要有以下几种情况: 1.重复定义全局变量。 可能存在两种情况: A、对于一些初学编程的程序员,有时候会以为需要使用全局变量的地方就可以使用定义申明一下。其实这是错误的...

Amamatthew
2014/06/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

偶遇 JDK 1.8 还未修复的 SecureRandom.getInstance("SHA1PRNG") 之 bug

楼主今天兴高采烈的在部署环境,下载 JDK,打包项目,上传至服务器。 配置 JDK ,打包上传项目楼主就不在这里重复了,读者自行解决哈! 1. 启动项目 java -jar xxxx.jar 令楼主没有想到的是:...

Ryan-瑞恩
20分钟前
8
0
【更新】Stimulsoft Reports v2019.3.1发布,新增对OData v4的支持功能

下载Stimulsoft Report.Ultimate v2019.3.1试用版 集所有报表解决方案于一体的综合性平台 Stimulsoft Reports.Ultimate是集所有报表解决方案于一体的综合性平台,拥有在JavaScript、ASP.NET...

xiaochuachua
20分钟前
1
0
JVM源码分析之javaagent原理完全解读

JVM源码分析之javaagent原理完全解读 概述 本文重点讲述javaagent的具体实现,因为它面向的是我们Java程序员,而且agent都是用Java编写的,不需要太多的C/C++编程基础,不过这篇文章里也会讲...

BryceLoski
26分钟前
1
0
git记住密码

git取消记住密码 git config --system --unset credential.helper git记住密码 git config --global credential.helper store...

大灰狼wow
27分钟前
2
0
java 面试知识点笔记(十四)异常体系

问:Error和Exception的区别? ps:Throwable上层是Object Error:程序无法处理的系统错误,编译器不做检查 Exception:程序可以处理的异常,捕获后可能恢复 RuntimeException:不可预知的,...

断风格男丶
30分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部