在C ++中将const放在头文件中是合法的,通常C方式是将extern声明放在头部和定义中只在一个编译单元中,但在C ++中,前一种技术导致增加二进制,因为链接时不删除符号(使用gnu ld和visual studio测试)。有没有办法做这些事情?我只能想到一个定义或C方式,但后者可能会为更少的优化提供空间......
piotr@gominola:0:/tmp$ g++ -c b.cc
piotr@gominola:0:/tmp$ g++ -c a.cc
piotr@gominola:0:/tmp$ nm a.o | c++filt | grep COOK
0000000000000000 r AI_LIKE_COOKIES
piotr@gominola:0:/tmp$ nm b.o | c++filt | grep COOK
0000000000000000 r AI_LIKE_COOKIES
piotr@gominola:0:/tmp$ g++ -o a a.o b.o
piotr@gominola:0:/tmp$ nm a | c++filt | grep COOK
0000000000400610 r AI_LIKE_COOKIES
0000000000400618 r AI_LIKE_COOKIES
piotr@gominola:0:/tmp$ cat a.h
#ifndef a_h
#define a_h
//const double A = 2.0;
//extern const double AI_LIKE_COOKIES;
const double AI_LIKE_COOKIES = 5.0;
#endif
piotr@gominola:0:/tmp$ cat a.cc
#include "a.h"
using namespace std;
extern void f();
//const double AI_LIKE_COOKIES = 2.0;
int main(int argc, char *argv[])
{
f();
}
piotr@gominola:0:/tmp$ cat b.cc
#include "a.h"
void f()
{
}
piotr@gominola:0:/tmp$
答案 0 :(得分:6)
声明为const
且未显式声明extern
的对象在C ++中具有内部链接。这意味着每个翻译单元都会获得它自己的对象副本。
但是,由于它们具有内部链接,因此无法从其他翻译单元命名,编译器可以检测对象本身是否未被使用 - 以及基本const
个对象这只是意味着它的地址永远不会被拿走;它的值可以根据需要替换 - 并从目标文件中省略它。
即使在-O1
,gcc也会执行此优化。
$ g++ -O1 -c a.cc
$ g++ -O1 -c b.cc
$ g++ -o a a.o b.o
$ nm a.o | c++filt | grep COOK
$ nm b.o | c++filt | grep COOK
$ nm a | c++filt | grep COOK
$
答案 1 :(得分:2)
你有两个真正的选择。您可以使用外部链接定义常量。通过内部链接,每个翻译单元中只有一个实际使用常量的副本,假设优化已打开。
内部联系:
// a.h
const double AI_LIKE_COOKIES = 5.0;
外部联系:
// a.h
extern const double AI_LIKE_COOKIES;
// a.c
const double AI_LIKE_COOKIES = 5.0;
然而,你可能会问,“内联常量怎么样?”不幸的是,浮点操作数无法真正内联。每当在函数中使用浮点常量时,该值将作为常量存储在内存中。考虑两个函数:
// In func1.c
double func1(double x) { return x + 5.7; }
// In func2.c
double func2(double x) { return x * 5.7; }
很可能两个文件都会在某处包含常量5.7,然后从内存加载。没有真正执行优化*。你得到5.7的两份副本,就像你这样做了一样:
extern const double CONSTANT_1, CONSTANT_2;
const double CONSTANT_1 = 5.7;
const double CONSTANT_2 = 5.7;
double func1(double x) { return x + CONSTANT_1; }
double func2(double x) { return x * CONSTANT_2; }
* 注意:在某些系统上,如果您知道常量将链接到同一个二进制映像而不是从库中加载,则会获得更小的代码。
建议:在标头文件中使用extern
,并在一个翻译单元中定义常量。代码可能不会更慢并禁止链接时优化,这是确保最终产品中只有一个副本最终的唯一好方法。
听起来好像超过8个字节的大惊小怪......
<强>汇编程序:强>
这是一个功能:
double func(double x)
{
return x + 5.0;
}
这是汇编程序,在x86_64上:
_Z4funcd:
.LFB0:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
addsd .LC0(%rip), %xmm0
ret
.cfi_endproc
.LFE0:
.size _Z4funcd, .-_Z4funcd
.section .rodata.cst8,"aM",@progbits,8
.align 8
.LC0:
.long 0
.long 1075052544
注意符号LC0
,它是一个包含值5.0的常量。内联除了使符号不可见之外什么也没做,所以它不会出现在nm
中。你仍然可以在每个使用常数的翻译单元中获得常量的副本。
答案 2 :(得分:0)
将const
隐含地放在每个标题中会使其成为内部链接,因此它在每个翻译单元中都是重复的。我相信“C路”是解决这个问题的正常方法。
您还可以使用简单的内联函数定义“常量”(请参阅std::numeric_limits<T>::max()
)
答案 3 :(得分:0)
这是合乎逻辑的行为。如果您需要使模块依赖外部名称,请改为使用extern
。在大多数情况下,不需要它。