开发者

c++ const symbols inflating linked file

开发者 https://www.devze.com 2023-04-09 14:03 出处:网络
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 techni

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号