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

时间:2011-09-29 14:22:38

标签: c++ linker constants const

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

4 个答案:

答案 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。在大多数情况下,不需要它。