传统认识
我们都知道,对于class 中static const int 的变量,可以在类中进行初始化,并省去外部的定义,向下面的这种方式;此时,编译,执行均是正确的。
class test
{
static const int tmp_int = 1;
public:
void print() {
printf("%d", tmp_int);
// foo(tmp_int);
//foo1(tmp_int);
//std::list<int> i_list;
//i_list.push_back(tmp_int);
}
};
意料之外
- 在函数模板中使用tmp_int
但是,当对tmp_int采用一些特殊用法时,程序链接过程中则会出现错误,代码如下:
template<class T>
void foo(const T &a)
{
printf("%d", a);
}
void foo1(int a)
{
printf("%d", a);
}
class test
{
static const int tmp_int = 1;
public:
void print() {
printf("%d", tmp_int);
foo(tmp_int);// 此处会报找到不tmp_int的定义
foo1(tmp_int);//正常通过编译连接
}
};
- 作为一个参数传递给需要引用类型实参的函数
void foo1(int &a)
{
printf("%d", a);
}
class test
{
static const int tmp_int = 1;
public:
void print() {
printf("%d", tmp_int);
// foo(tmp_int);
foo1(tmp_int);//此处报错,同上
std::list<int> i_list;
//i_list.push_back(tmp_int);//此处同样会报错,现象和原因同上
}
};
情理之中
上诉现象的主要原因可以通过两点进行概括:1.在class中对tmp_int进行初始化,并不会对tmp_int真正的分配地址;2.无论是模板还是引用,都需要地址。
查看tmp_int的符号表,只在类中进行初始化,得到如下结果:
tingshuai@yantingshuaideMacBook-Air ~$ nm test.o
0000000000000070 S __Z3fooIiEvRKT_
0000000000000030 S __ZN4test5printEv
U __ZN4test7tmp_intE
0000000000000000 T _main
U _printf
在类外加上tmp_int的定义,结果如下:
tingshuai@yantingshuaideMacBook-Air ~$ nm test.o
0000000000000070 S __Z3fooIiEvRKT_
0000000000000030 S __ZN4test5printEv
000000000000009c S __ZN4test7tmp_intE
0000000000000000 T _main
U _printf
谍影重重
- 编译器对class中的static const int 如何处理的?为什么会没有地址
- 模板编译时需要实例化(是在编译时?需要进一步确定),实例化需要参数的地址来获取变量类型?那为何当为函数模板赋值一直常量时可以获取类型信息,而tmp_int不可以?
template<class T>
void foo(const T &a)
{
printf("%d", a);
}
void foo1(int &a)
{
printf("%d", a);
}
class test
{
static const int tmp_int = 1;
public:
void print() {
printf("%d", tmp_int);
foo(3);//可以通过编译连接,执行结果正确
// foo1(3);
std::list<int> i_list;
//i_list.push_back(tmp_int);//tmp_int报错,传递常量则没有问题
}
};
参考
https://bytes.com/topic/c/answers/850988-const-static-member
http://stackoverflow.com/questions/5391973/undefined-reference-to-static-const-int
http://www.cplusplus.com/forum/general/36478/
刨根问底
- 通过观察汇编可知,对于模板的代码是在编译阶段生成的(其实这是想当然的事情,难道在程序的执行过程中能生成代码,只能对自己以前的理解呵呵了);只不过是当模板函数或模板类被真正调用时,才会为其生成真正的代码段;如下面的代码
template<class T>
void foo(const T &a)
{
printf("%d", a);
}
int main(int argc, char *argv[])
{
foo(3);//第一次调用目标函数
double d = 1.245;
foo(d);//第二次调用
return 0;
}
其汇编如下:
void foo(const T &a)
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 48 89 7d f8 mov %rdi,-0x8(%rbp)
{
printf("%d", a);
c: 48 8b 45 f8 mov -0x8(%rbp),%rax
10: 8b 00 mov (%rax),%eax
12: 89 c6 mov %eax,%esi
14: bf 00 00 00 00 mov $0x0,%edi
19: b8 00 00 00 00 mov $0x0,%eax
1e: e8 00 00 00 00 callq 23 <_Z3fooIiEvRKT_+0x23>
}
23: c9 leaveq
24: c3 retq
Disassembly of section .text._Z3fooIdEvRKT_:
0000000000000000 <_Z3fooIdEvRKT_>:
template<class T>
void foo(const T &a)
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 48 89 7d f8 mov %rdi,-0x8(%rbp)
{
printf("%d", a);
c: 48 8b 45 f8 mov -0x8(%rbp),%rax
10: f2 0f 10 00 movsd (%rax),%xmm0
14: bf 00 00 00 00 mov $0x0,%edi
19: b8 01 00 00 00 mov $0x1,%eax
1e: e8 00 00 00 00 callq 23 <_Z3fooIdEvRKT_+0x23>
}
23: c9 leaveq
24: c3 retq
显而易见,生成了两个模板函数foo的实例化