c++ const symbols inflating linked file
In C++ is legal to put a const in the header file, usually the C way would be to put the extern declaration in the header and the definition in just one compilation unit, but in C++, the former technique leads to an increased binary since the symbols are not removed while linking (tested with gnu ld and visual studio). Is there a good way to do these things? I can only think of a define or the C way, but the later might give room to less optimizations...
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$
Objects declared const
and not explicitly declared extern
have internal linkage in C++. This means that each translation unit gets it's own copy of the object.
However, as they have internal linkage and so can't be named from other translation units, the compiler can detect if the object itself is not used - and for basic const
objects this just means if it's address is never taken; it's value can be substituted as needed - and omit it from the object file.
gcc will perform this optimization even at -O1
.
$ 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
$
There are two real choices you have. You can define a constant with external linkage, or not. With internal linkage, you'll only a copy in each translation unit that actually uses the constant, assuming optimization is turned on.
Internal linkage:
// a.h
const double AI_LIKE_COOKIES = 5.0;
External linkage:
// a.h
extern const double AI_LIKE_COOKIES;
// a.c
const double AI_LIKE_COOKIES = 5.0;
However, you may be asking, "what about inlined constants?" Unfortunately, floating point operands can't really be inlined. Whenever you use a floating point constant in a function, that value gets stored as a constant in memory. Consider the two functions:
// In func1.c
double func1(double x) { return x + 5.7; }
// In func2.c
double func2(double x) { return x * 5.7; }
In all likelihood, both files will contain a constant 5.7 somewhere, which is then loaded from memory. No optimization is really performed*. You get two copies of 5.7, just as if you had done this:
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; }
* Note: On some systems, you get smaller code if you know that the constant will be linked into the same binary image rather than loaded from a library.
Recommendation: Use an extern
in the header file, and define the constant in one translation unit. The code likely won't be any slower and barring link-time optimizations, this is the only good way to make sure only one copy ends up in the final product.
It sounds like a lot of fuss over eight bytes, though...
Assembler:
Here's a function:
double func(double x)
{
return x + 5.0;
}
Here's the assembler, on 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
Notice the symbol LC0
, which is a constant containing the value 5.0. Inlining has done nothing but make the symbol invisible so it doesn't show up in nm
. You still get a copy of the constant sitting around in every translation unit which uses the constant.
Putting the const
in each header implicitly makes it internal linkage so it's duplicated in every translation unit. The "C way" is the normal way of dealing with this I believe.
You can also define "constants" with trivial inline functions (see std::numeric_limits<T>::max()
)
It's logical behaviour. If you need to make your module dependent on external name include extern
instead. In most cases it's not needed.
精彩评论