C++中的static const

主页 > 新闻资讯 > 软件开发资讯 > C++编程C++软件开发 >

C++中的static const

提出问题

以下代码,能编译链接通过吗?

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 l    df *ABS*  00000000 test.cpp
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack
00000000 l    d  .eh_frame  00000000 .eh_frame
00000000 l    d  .comment   00000000 .comment
00000000 g     F .text  00000005 _Z1fRKi
00000005 g     F .text  00000019 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代替了。


解决问题

知道原因了,那么怎么解决呢?恩,至少三种方法:

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

那么,这种情况下,编译器是如何处理的呢?就留给读者诸君作为练习吧。



上一篇:C++中std::map的使用   下一篇: C# .net MVC 微信红包