LINUX.ORG.RU

Хочу разобраться с таблицами экспорта в C/C++ библиотеках/исп. файлах.


0

4

Интересуют платформы: linux, windows, macOS. Последние 2 знаю хуже. Меня интересуют не спецификации или «как правильно», а как оно в реальности и какие есть побочные явления/возможности. Что я помню:

Компилятор выдаёт один объектный файл на каждую единицу трансляции - .cpp файл с кучей включенных .h. В этот объектный файл попадает таблица экспорта - набор «символов» (строк/имён) с атрибутами и адресом для каждого символа. Имена функций, глобальных переменных и т.п. Адрес - всмысле по какому смещению от адресного пространства процесса или какой-то секции (напр. секции кода, секции данных) данный символ будет лежать в памяти. Атрибуты - это код/данные/другое.

Когда из набора объектных файлов линкуется один .so/.dll/.a/.exe, наши таблицы экспорта из всех объектных файлов тупо сливаются в одну. Если какой-то символ где-то дублируется, линкер матерится (как оно под разными платформами)?

Есть ли в таблице экспорта какая-то «разная степень видимости»? Т.е. могут там лежать символы public, private или ещё что-то такое хитрое? Незнаю для чего! Есть ли там что-то такое или всё попавшее в таблицу экспорта видимо извне линкером?

Сколько в каждом .obj / .so / .dll - файле таблиц экспорта? Одна на весь файл или у неё существуют разные разделы?

C++ - классы выступают как пространства имён для «символов». Плюс сами namespace-ы. Для них есть какие-то правила формирования имён символов или каждый компилятор заворачивает двойные двоеточия ("::") как-то по-своему?

Что сделать в C/C++ - программе, чтобы обеспечить символу присутствие в таблице экспорта? Это будет любая функция или функция-член класса и любая глобальная переменная? Правильно я понимаю, что static глобальные переменные не попадут в таблицу экспорта, обеспечивая локальность символа для данной единицы трансляции? А если надо заюзать глобальную переменную из другой единицы трансляции, то нужно объявить эту переменную со словом extern?

Я гарантированно где-то неправ, т.к. давно не думал об этом, поправьте/дополните пожалуйста. Плюс интересно почитать что-нибудь касающееся сабжа, но что у меня не упомянуто и особо интересно почитать о различиях линковальной кухни под разными платформами, о платформенно-зависимых ключевых словах языков C/C++ под разными платформами: о виндовых «__declspec(dllexport)» и прочей такой тряхомути.

Что такое hidden visibility в gcc? Ладно, погуглю... )

Для них есть какие-то правила формирования имён символов или каждый компилятор заворачивает двойные двоеточия ("::") как-то по-своему?

ЕМНИП, да.

Правильно я понимаю, что static глобальные переменные не попадут в таблицу экспорта, обеспечивая локальность символа для данной единицы трансляции?

Да.

А если надо заюзать глобальную переменную из другой единицы трансляции, то нужно объявить эту переменную со словом extern?

Нет, если переменная статическая, то она доступна только из этого файла. Никак ты её из другого не заюзаешь.

Что такое hidden visibility в gcc?

http://gcc.gnu.org/wiki/Visibility

А вообще, подписался на тред, ждём местных гуру.

PS На тему visibility ещё вот

DELIRIUM ☆☆☆☆☆ ()
Последнее исправление: DELIRIUM (всего исправлений: 1)
Ответ на: комментарий от DELIRIUM

Нет, если переменная статическая, то она доступна только из этого файла. Никак ты её из другого не заюзаешь.

Я имел ввиду не статическую, а обычную глобальную. Щас проверил - я прав.

t1.cpp


#include <iostream>
//int variable = 1;
extern int variable;

int f_t2(int);

int main(void)
{
	std::cout << "variable " << variable << "\n";
	std::cout << f_t2( 2 ) << "\n";	
	return 0;
}

t2.cpp:

#include <iostream>

int variable = 2;

int f_t2(int _x)
{

	std::cout << "\nf_t2()\n";
	return _x*2;
	
}

xx@(none) ~ $ g++ t1.cpp t2.cpp -o t
xx@(none) ~ $ ./t 
variable 2

f_t2()
4
kiverattes ★☆ ()

Если какой-то символ где-то дублируется, линкер матерится (как оно под разными платформами)?

А при чём тут платформа? Один и тот же ld может работать и под виндами, и под макосью, и даже под линухом.

Есть ли в таблице экспорта какая-то «разная степень видимости»?

Насколько я помню, нет.

Для них есть какие-то правила формирования имён символов или каждый компилятор заворачивает двойные двоеточия ("::") как-то по-своему?

Каждый по-своему. Поэтому, в частности, если нужно совместно использовать код, сделанный разными компиляторами, то приходится делать C-обёртки (extern «C»).

Что сделать в C/C++ - программе, чтобы обеспечить символу присутствие в таблице экспорта?

Да, в общем, ничего. Пляски нужны, чтобы символ в таблицу экспорта не попал.

Я, правда, не знаю — локальный класс, объявленный внутри функции, в таблицу экспорта попадёт? В принципе, он там ни для чего не нужен.

Miguel ★★★★★ ()
Ответ на: комментарий от Miguel

Чето я не понял:

struct A {
   void f() {} 
};

namespace {
    struct NA {
        void f() {}
    };
}

void f() {
    struct FA {
        void f() {}
    };
    FA fa;
    fa.f();
    A a;
    a.f();
    NA na;
    na.f();
}
$ g++ -c test.cpp
$ nm -gC test.o 
0000000000000014 T f()
0000000000000000 W A::f()

То есть попадает только функция f() и метод A::f(). Почему не попадает NA, понятно. Классы не экспортируются чтоли? Или я как-то не так смотрю?

DELIRIUM ☆☆☆☆☆ ()

Если какой-то символ где-то дублируется, линкер матерится

Если символ дублируется в библиотеках - ругаться не будет. К тому же, бывают слабые и общие символы - они могут дублироваться (man nm).

unsigned ★★★ ()
Ответ на: комментарий от Miguel

Про анонимный неймспейс понятное дело. Я имею в виду, экспортируются только имена функций? Почему не экспортируются классы полностью: конструкторы, деструкторы?

DELIRIUM ☆☆☆☆☆ ()
Ответ на: комментарий от DELIRIUM

Может быть компилятор решил не генерировать какой-либо код конструктора и деструктора вообще, ибо ваши структуры представляют собой сущности размером в ноль байт, где нечего конструировать и деконструировать? Это в стандарте написано, что у каждого класса/структуры обязательно будет конструктор, на крайняк умолчательный. Но компиляторы помимо стандарта взаимодействуют с реальным миром, где часто есть возможность оптимизации. Компилятор посмотрел на вашу структуру и подумал: пускай чувак считает, что у него есть умолчательный конструктор, который ничего не делает. Но его реализация ничем не будет отличаться от полного его отсутствия: я конечно могу ему родить функцию с 10 инструкциями «nop» внутри, но нахрена козе боян?

kiverattes ★☆ ()
Последнее исправление: kiverattes (всего исправлений: 3)
Ответ на: комментарий от DELIRIUM

А, понял. Так оно просто не нужно. Ни конструктор, ни деструктор ни фига не делают, и компилятор это прекрасно знает.

Другой вопрос, если ты напишешь конструктор лично, пусть даже пустой. Или, дабы повыпендриваться, добавишь, скажем, инициализируемое поле:

struct B {
   B() {}
};

struct A {
   void f() {}
   B b;
};

namespace U {
    struct NA {
        void f() {}
    };
}

void f() {
    struct FA {
        void f() {}
    };
    FA fa;
    fa.f();
    A a;
    a.f();
    U::NA na;
    na.f();
}
Тогда на выходе ты получишь
00000005 T f()
00000000 T A::f()
00000000 T A::A()
00000000 T B::B()
00000000 T U::NA::f()
Обрати внимание на экспортированный конструктор A::A().

Штука в том, что по одному лишь описанию структур (обычно - в заголовочном файле) компилятор может понять, нужно ли экспортировать конструктор; а это нужно, как раз, в том случае, когда конструктор специфицирован явно у самой структуры, у одного из её членов или у одного из её предков. Соответственно, компилируя тот же заголовочный файл ещё раз (в составе другого c-файла), компилятор сделает тот же вывод, и отсутствующий конструктор вызвать не попытается.

Miguel ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.