提出问题以下代码,能编译链接通过吗?
void f(const int &value)
{
}
class Test
{
public:
static const int a = 1;
};
int main()
{
f(Test::a);
return 0;
}
我的第一感觉是:应该没问题,吧。在VS 2013 实验了下,顺利通过,一切正常.然而gcc下是报错的:Undefined reference to ‘Test::a’这是为什么?
分析问题写作本文时所用的环境是gcc 4.8.2 (Ubuntu 14.04 , X86平台)。注意,本文的讨论只针对类的static const成员,也就是所谓的class scope。namespace scope的情况不属于我们的讨论范围内。
把以上代码保存为test.cpp,然后用gcc编译它:
g++ -c -o test.o test.cpp
这个命令执行之后,我们会在目录下得到test.o文件。接着,我们通过objdump来查看符号表:
objdump -x test.o
我们可以看到类似如下的输出:
SYMBOL TABLE:
00000000 ldf *ABS*00000000 test.cpp
00000000 ld.text00000000 .text
00000000 ld.data00000000 .data
00000000 ld.bss00000000 .bss
00000000 ld.note.GNU-stack00000000 .note.GNU-stack
00000000 ld.eh_frame00000000 .eh_frame
00000000 ld.comment00000000 .comment
00000000 gF .text00000005 _Z1fRKi
00000005 gF .text00000019 main
00000000*UND*00000000 _ZN4Test1aE
上面的最后一行,_ZN4Test1aE就是对应我们程序中声明的Test::a。之所以长得这么复杂、奇怪,是因为编译器做了mangling处理。
注意到UND没?根据文档的解释:
UND : if the section is referenced in the file being dumped, but not defined there
也就是_ZN4Test1aE在本.o文件中引用,然而它却木有定义。因此,报Undefined reference to ‘Test::a’的错,也就情理之中了。
那么,我们的程序是否真的引用了_ZN4Test1aE呢?恩,我们接着往下看。
输入如下命令:
g++ -S test.cpp
我们可以得到类似这样的汇编代码(做了整理,节选):
main:
pushl%ebp
movl%esp, %ebp
subl$4, %esp
movl$_ZN4Test1aE, (%esp) ;
看到没?_ZN4Test1aE !
call_Z1fRKi ;
调用函数f
movl$0, %eax
leave
ret
虽然我们已经分析出为什么会报错,然而,我们还有一个疑问,就是,为什么下面的代码,是OK的?
void f(const int value) //这里没有 &
{
}
class Test
{
public:
static const int a = 1;
};
int main()
{
f(Test::a);
//没问题
return 0;
}
恩,有了前面的基础,相信读者诸君已经知道怎么分析。我们可以用同样的方法,看看它的汇编代码:
main:
pushl%ebp
movl%esp, %ebp
subl$4, %esp
movl$1, (%esp);
看到没?1,不是_ZN4Test1aE,也不是Test::a
call_Z1fi
movl$0, %eax
leave
ret
也就是说,在这里,编译器只是把Test::a认作一个占位符,实际使用之处用1代替了。
解决问题【【Thinking|C++中的static const】知道原因了,那么怎么解决呢?恩,至少三种方法:
1.我们可以定义(而不是声明)Test::a。是的,上面的static const int a = 1; 并不是它的定义式。如果要定义,那么我们应该这么做:
void f(const int &value)//还是传引用
{
}
class Test
{
public:
static const int a;
};
const int Test::a = 1;
//定义a
int main()
{
f(Test::a);
//现在没问题了
return 0;
}
有兴趣的读者可以看看这个程序对应的符号表,就会发现Test::a被放在了程序的rodata section,而不是UND了。
2.如果仅仅声明a,那么我们可以按值的方式使用它,这没问题。也就是只使用它的值;而不去获得它的地址(当然,也包括引用。引用本质上也是地址)。
3.使用枚举类型。是的,枚举!像这样:
void f(const int &value)//还是传引用
{
}
class Test
{
public:
enum HELLOWORLD {a = 1};
//枚举,而不是 static const
};
int main()
{
f(Test::a);
//没问题
return 0;
}
那么,这种情况下,编译器是如何处理的呢?就留给读者诸君作为练习吧。
关于程序设计基石与实践更多讨论与交流,敬请关注本博客和新浪微博songzi_tea
推荐阅读
- c/c++|有感 Visual Studio 2015 RTM 简介 - 八年后回归 Dot Net,终于迎来了 Mvc 时代,盼走了 Web 窗体时代...
- C/C++|C/C++ basis 02
- Qt实战|Qt+OpenCV联合开发(二十一)--图像翻转与旋转
- Qt实战|Qt+OpenCV联合开发(十四)--图像感兴趣区域(ROI)的提取
- Qt实战|Qt+OpenCV联合开发(十三)--通道分离与合并
- opencv|Qt+OpenCV联合开发(十六)--图像几何形状绘制
- Qt实战|Qt+OpenCV联合开发(十七)--随机数与随机颜色
- SNAT的MASQUERADE地址选择与端口选择
- IPTABLES的连接跟踪与NAT分析
- IPVS分析