项目场景:
要求写一个编解码模块S,该模块需要被模块L和模块C使用到。模块S中有一个全局表,模块L和模块C都需要使用该全局表。
问题描述:
模块C首先初始化了共享模块S中的全局变量表,当模块L尝试访问这个全局变量的时候,发现这个表中的内容为空。这与我想象中的结果不一致。全局变量不是已经被初始化了吗?为什么表中的内容为空呢?
@ Share.c
int g_desc[3] = {0};
void Init_Gdesc()
{
g_desc[0] = {1};
g_desc[1] = {1};
g_desc[2] = {1};
}
@ Module_L.c
Init_Gdesc();
@ Module_C.c
if(g_desc[0] == 0) {
printf("BAD");
} else {
printf("GOOD");
}
output:BAD
原因分析:
最后问了旁边的同事才搞清楚问题出在哪里。
模块L和模块C属于不同的进程,他们各自有自己独立的虚拟地址空间。当他们都链接了共享库S的时候,实际上代码中都保存了一份全局变量g_desc的副本。
对于这句话,我是这么理解的。每个进程有自己独立的虚拟地址空间,在链接共享库S的时候,把全局变量g_desc的地址也拷贝了一份。所以两个进程访问的全局变量实虽然名字一摸一样,但是内容完全是两份。
所以当模块C初始化了全局变量g_desc,实际上也只是初始化了自己内存中保存的那个全局变量g_desc。而当模块L访问自己的全局变量g_desc的时候,实际上是没有被初始化过的。
解决方案:
解决方案有两种:
解决方案一: 模块L也调用一下初始化函数
@ Module_C.c
Init_Gdesc();
if(g_desc[0] == 0) {
printf("BAD");
} else {
printf("GOOD");
}
output:GOOD
解决方案二: 让全局变量在共享模块中初始化,模块L和C直接调用
@ Share.c
int g_desc[3] = {1, 1, 1};
@ Module_L.c
if(g_desc[0] == 0) {
printf("BAD");
} else {
printf("GOOD");
}
output:GOOD
对于模块L和C而言,如果都是希望使用初始化后的全局变量表g_desc,那么方法2明显是更好的方法。也是我最后采取的方法。至于方案一,如果两个模块对于全局变量初始化的条件有需求的话,可以选择采用。
看了同事的代码,同事对于方案一,采取的一个技巧也值得学习一下。如果全局变量表是个注册表,且非常复杂的话,可以构造一个头文件,完全用来存放表中的内容。这样只要把这个头文件include,就可以直接初始化该表。
@ header.h
{
REG_LIST(idx1, type1),
REG_LIST(idx2, type2),
REG_LIST(idx3, type3)
};
@ Share.c
int g_desc[3] = #include "header.h"
结论:
产生这个BUG的原因还是因为自己作为C语言的初学者,对于C语言编译和链接过程做了什么事不熟悉。不同的模块如果属于不同的进程,那么这两个模块所处的地址空间是完全独立的。对于使用的同样的全局变量,不同的进程维护了不同的副本。