c++ const 符号膨胀链接文件

发布于 2024-12-07 08:59:54 字数 1049 浏览 0 评论 0原文

在 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$

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$

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

淡水深流 2024-12-14 08:59:54

声明为 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
$ 

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
$ 
赏烟花じ飞满天 2024-12-14 08:59:54

您有两个真正的选择。您可以定义具有外部链接的常量,也可以不定义。通过内部链接,您将仅在每个实际使用常量的翻译单元中获得一个副本,假设优化已打开。

内部链接:

// 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 中。您仍然会在每个使用常量的翻译单元中获得该常量的副本。

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.

神魇的王 2024-12-14 08:59:54

const 放入每个标头中会隐式使其成为内部链接,因此它会在每个翻译单元中重复。我相信“C 方式”是处理这个问题的正常方式。

您还可以使用简单的内联函数定义“常量”(请参阅​​ std::numeric_limits::max())

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())

策马西风 2024-12-14 08:59:54

这是合乎逻辑的行为。如果您需要使模块依赖于外部名称,请改为包含 extern 。大多数情况下是不需要的。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文