VC++全局变量初始化

时间:2022-12-22 09:34:52

目录

第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;

}

程序载入内存,全局变量a、b、c就完成了静态初始化(static initialization),此时a、b、c的数值分别为0、1、0。

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

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

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

1.2 强符号、弱符号

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

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

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

强符号是不会被合并的。如:1.cpp、2.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();

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

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

全局变量默认在段.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_RECV和WM_SOCKET_RECV重复,特使用GetMsgId函数对它们进行动态初始化。

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

VC++6.0里展开BEGIN_MESSAGE_MAP、ON_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_RECV、WM_SOCKET_RECV、CDlgMain::_messageEntries均在段.CRT$XCU里,所以它们的初始化顺序是不可预知的。

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

为了让WM_SERIAL_RECV、WM_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_RECV、WM_SOCKET_RECV在段.CRT$XCL里,而CDlgMain::_messageEntries在段.CRT$XCU里。这样就能保证CDlgMain::_messageEntries动态初始化前WM_SERIAL_RECV、WM_SOCKET_RECV已经动态初始化完毕。CDlgMain也就能够正常处理WM_SERIAL_RECV、WM_SOCKET_RECV消息了。

VC++全局变量初始化的更多相关文章

  1. c&plus;&plus; 全局变量初始化的一点总结

    注意:本文所说的全局变量指的是 variables with static storage,措词来自 c++ 的语言标准文档. 什么时候初始化 根据 C++ 标准,全局变量的初始化要在 main 函数 ...

  2. 一个可遇不可求的 bug 全局变量初始化顺序问题 哈哈

    这是今天下午帮同事查的一个客户端 C++ 的 bug,前人留下的谜之代码.. 具体情况是,客户端实现了有一个简单的内存池,每次申请内存的时候会把新申请到的内存信息存到一个 map 里,据说是为了检查内 ...

  3. ServletContext全局变量初始化

    Java部分 package com.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.ser ...

  4. &lbrack;转&rsqb;C&sol;C&plus;&plus;关于全局变量和局部变量初始化与不初始化的区别

    原文链接:http://www.kingofcoders.com/viewNews.php?type=newsCpp&id=189&number=4836955386 在C语言里,全局 ...

  5. dll加载过程全局变量会先初始化

    在一个生成dll的工程中看到一个文件只有一句全局变量初始化的代码,很好奇为什么这句代码在dll加载的时候就会执行,因此断点调试发现 __declspec(noinline) BOOL __cdecl ...

  6. c&plus;&plus;中局部变量初始化的问题

    在C语言里,全局变量如果不初始化的话,默认为0,也就是说在全局空间里: int x =0; 跟 int x; 的效果看起来是一样的.但其实这里面的差别很大,强烈建议大家所有的全局变量都要初始化,他们的 ...

  7. VC&plus;&plus;中的C运行时库浅析(控制台程序默认使用单线程的静态链接库,而MFC中的CFile类已暗藏了多线程)

    1.概论 运行时库是程序在运行时所需要的库文件,通常运行时库是以LIB或DLL形式提供的.C运行时库诞生于20世纪70年代,当时的程序世界还很单纯,应用程序都是单线程的,多任务或多线程机制在此时还属于 ...

  8. 浅谈C语言中结构体的初始化

    转自:http://www.jb51.net/article/37246.htm <代码大全>建议在变量定义的时候进行初始化,但是很多人,特别是新人对结构体或者结构体数组定义是一般不会初始 ...

  9. iPhone开发之全局变量的使用

    全局变量历来就是很好的东西,能够在开发中带来很多方便,下面来介绍一下iPhone中软件开发时全局变量的使用方法: 一.新建Constants.h文件(文件名根据需要自己取),用于存放全局变量: 二.在 ...

随机推荐

  1. WebConfig配置

    安全配置示例 https://msdn.microsoft.com/zh-cn/magazine/gg309184.aspx

  2. 学堂在线 UWP 首版

    好久没有写博客了,主要是最近在写一个小小的App.<( ̄︶ ̄)> 不知道看各位有木有爱看慕课的,作为一名资深的大三学渣的我有看慕课的习惯.一直在看学堂在线的慕课,感觉质量确实还可以,但是遗 ...

  3. linux服务之udevd

    http://www.ibm.com/developerworks/cn/linux/l-cn-udev/[root@localhost ~]# uname -r2.6.32-431.el6.x86_ ...

  4. Hibernate征途(五)之继承映射和组件映射

    之所以把这两种映射放到一起说,是因为二者都是以复用为目的,减少了代码和配置量,这是相同点:二者之间的不同点类似继承和实现的区别:继承的类是一个事物的抽象,而实现的接口仅仅是功能的抽象. 继承映射 如上 ...

  5. 优化css选择器

    1.css选择器效率排行从高到低如下: id选择器(#head) 类选择器(.content) 标签选择器(p,h1) 相邻选择器(h1+p) 子选择器(ul < li)

  6. 现代编译原理——第六章:中间树 IR Tree 含源码

    转自: http://www.cnblogs.com/BlackWalnut/p/4559717.html 这一章,就虎书而言,理论知识点是及其少的,就介绍了为什么要有一个中间表示树.看下面这张图就能 ...

  7. JSP&lpar;3&rpar;—Cookie和Session

    HTTP是一个无状态的协议,web服务器无法分辨出那些请求是同一个浏览器发出的,浏览器每一次请求都是孤立的 即使HTTP1.1支持持续链接,但当用户有一段时间没有请求时,连接也会关闭. 如何实现网上的 ...

  8. 数据结构开发&lpar;14&rpar;:KMP 子串查找算法

    0.目录 1.KMP 子串查找算法 2.KMP 算法的应用 3.小结 1.KMP 子串查找算法 问题: 如何在目标字符串S中,查找是否存在子串P? 朴素解法: 朴素解法的一个优化线索: 示例: 伟大的 ...

  9. 转:windows 下 netsh 实现 端口映射(端口转发)

    本文转自:本文出自 “httpyuntianjxxll.spac..” 博客,请务必保留此出处http://333234.blog.51cto.com/323234/1135361 -----hapr ...

  10. Android学习——文件存储

    在Andriod开发中,文件存储和Java的文件存储类似.但需要注意的是,为了防止产生碎片垃圾,在创建文件时,要尽量使用系统给出的函数进行创建,这样当APP被卸载后,系统可以将这些文件统一删除掉.获取 ...