c ++ const символы раздувают связанный файл - PullRequest
2 голосов
/ 29 сентября 2011

В 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 ]

6 голосов
/ 29 сентября 2011

Объекты, объявленные const и не объявленные явно extern, имеют внутреннюю связь в C ++. Это означает, что каждая единица перевода получает свою собственную копию объекта.

Однако, поскольку они имеют внутреннюю связь и поэтому не могут быть названы из других блоков перевода, компилятор может определить, не используется ли сам объект - а для базовых const объектов это просто означает если его адрес никогда не берется; это значение может быть заменено при необходимости - и пропустить его из объектного файла.

gcc выполнит эту оптимизацию даже при -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
$ 
2 голосов
/ 29 сентября 2011

У вас есть два реальных выбора. Вы можете определить константу с внешней связью, или нет. С внутренней связью у вас будет только копия в каждой единице перевода, которая фактически использует константу, при условии, что оптимизация включена.

Внутренняя связь:

// 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 в заголовочном файле и определите константу в одной единице перевода. Скорее всего, код не будет медленнее и не позволит оптимизировать время соединения, это единственный хороший способ убедиться, что в конечном продукте окажется только одна копия.

Звучит как суета на протяжении восьми байт, хотя ...

Ассемблер:

Вот функция:

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. Вы по-прежнему получаете копию константы, сидящей в каждой единице перевода, которая использует константу.

0 голосов
/ 29 сентября 2011

Это логичное поведение.Если вам нужно сделать свой модуль зависимым от внешнего имени, включите вместо него extern.В большинстве случаев это не нужно.

0 голосов
/ 29 сентября 2011

Помещение const в каждый заголовок неявно делает его внутренней связью, поэтому он дублируется в каждой единице перевода.Я считаю, что «путь С» - это нормальный способ справиться с этим.

Вы также можете определить «константы» с помощью тривиальных встроенных функций (см. std::numeric_limits<T>::max())

...