Pull to refresh

Comments 38

Народ, ну что за дела. Офигительнейшая статья, огромный перевод. Статья висит час(!) Десять плюсов, 30 человек положило в избранное, и карма автора 0. Ноль голосов, т.е. вообще никто не поблагодарил его. Это очень приятно получать карму за хорошую статью, помогите человеку.
UFO just landed and posted this here
Хорошую статью всем прочитать полезно. Как говорится, повторение — мать учения.
Что-то я не вижу связи между «новичок в С/С++» и «новичок на Хабре».
А также не совсем прямая связь между «новичок на Хабре» и «карма» :)
Долго не плюсовали, так как вчитывались. И правда, замечательная статья.
Как дочитал, так сразу плюсанул. Всё логично :)
UFO just landed and posted this here
Кому как, мне компоновка более традиционна.
Спасибо. На самом деле долго думал, как писать «компоновка» или «линковка». В разговоре сам, наверное, чаще использую «линковка», но когда читаю, «компоновка» приятнее глазу и звучит по-русски.
Отличнейшая статья, качественный перевод, прочитал на одном дыхании. Весьма всеобъемлюще. Поздравляю с успешным стартом на хабре =)
А как делать содержание со ссылками на подпункты в этой же статье?
Статья — отличная! Вроде и знаешь, как это все работает, а читаешь и понимаешь, что узнал что-то новое.
Спасибо за хорошую статью!
Нового сам ничего не узнал, но то что знал «причесалось» и разложилось по полочкам :)
> все символы, произведённые C компилятором, выглядят так же, как и в исходном коде

It depends.

В Linux — да. В Darwin — нет, добавляется "_".
> Как обычно, мы можем увидеть здесь кучу разных вещей, но одна из них наиболее интересна для нас это записи с классом W (что означает «слабый» символ («weak» symbol)) а также записи именем секции типа ".gnu.linkonce.t.stuff". Это маркеры для конструкторов глобальных объектов

Конструктор Fred::Fred() помечен как weak потому что он неявно объявлен как inline, и компилятор будет генерировать для него код в кажой единице трансляции, где есть это определение, а значит линковщик должен убрать лишние копии этой функции.
Про библиотеки в Windows написана полная чушь.

Во-первых, вся информация о экспортируемых DLL символах все-таки содержится внутри. Соответственно, файл .LIB можно восстановить по файлу .DLL без особых проблем.

Если компоновщик ld позволяет просто подключить разделяемую библиотеку как входной файл, самостоятельно разбираясь, что библиотека является разделяемой и не требует статической компоновки, то компоновщик link.exe требует, чтобы все входные файлы были статическими библиотеками и никак иначе, и именно отсюда растут ноги у .LIB файла.

Кстати, тот же ld, портированный под Windows, спокойно есть .DLL файлы и не требует никаких либов, так что .LIB-файлы являются особенностью не операционной системы, а компоновщика.

PS В ситуации с циклическими зависимостями также существует альтернативное решение. Можно описать все импортируемые функции через __declspec(dllimport) и вообще не использовать .LIB-файлы.
Если не секрет, как восстановить lib-файл по dll? Не раз сталкивался с тем, что dll есть, а lib производитель не удосужился приложить.
ImpLib32. Когда-то входил в состав Borland C++ Builder, сейчас вроде можно в интернете найти.
implib32, polib…

При необходимости можно это сделать и самостоятельно. Если известен список экспортируемых функций, то надо перечислить прототипы этих функций в отдельном файле с модификатором __declspec(dllimport). После этого результат необходимо скомпилировать и сделать из этого всего статическую библиотеку. Вот и все, .LIB-файл готов.

Но один .LIB файл бессмыслен, к нему должен прилагаться заголовочный. Вот его получить уже труднее, поскольку используемые типы данных в DLL не описаны. С другой стороны, если заголовочный файл уже есть, то зачастую можно перед его включением написать что-то типа #define MYLIBAPI __declspec(dllimport), и получить требуемый в прошлом пункте файл с исходным кодом совершенно бесплатно. Или же можно вообще отказаться от использования .LIB-файла.
Очень полезная статья. Поправте, пожалуйста, в тексте — Скотт Майерс.
Для новичка статья избыточна.
Важные вещи которые стоит запомнить для линукса:
1) Линкер однопроходный и обрабатывает строку линковки слева-направо. Поэтому при линковке важнен порядок объектных файлов и библиотек. Включить многопроходную линковку в пределах группы можно с помощью: --Wl,--start-group… -Wl,--end-group — внутри группы линкер станет многопроходным и возможно разрешение кросс-зависимостей;
2) При линковке динмаических библиотек линкер смотрит архитектуру. Невозможно слинковать в 1 бинарь разные архитектуры: x86_84 и i386 не слинкуется, одна из них будет отброшена. Проверяйте свой LD_LIBRARY_PATH. Допускается в пути иметь разные архитектуры, линкер сам выберет подходящую.
3) Множественное объявление символа (multiple definition of) скорее всего не ошибка, лечится -Wl,-z,muldefs.

Поиск потерявшегося символа — в какой библиотеке вполне можно делать и через Midnight-поиск, можно через nm |grep — как вам удобнее. Обычно если линковка сломана нужно несколько итераций линковка-поиск-исправление мейкфайла, дописываем найденные библиотеки справа. Допускается повторение библиотек в строке линковки, обычно при этом самый правый повтор можно убрать как избыточный (за исключением случая с кросс-сылками).
За статью спасибо. Перевод отличный, видно, что человек хорошо владеет родным языком и терминологией, не скатываясь в англицизмы.
Не совсем понял пример:
Fred theFred;
Fred theOtherFred(55);

theFred |00000000| B | OBJECT|00000008| |.bss
theOtherFred |00000008| B | OBJECT|00000008| |.bss
global constructors keyed to theFred

Вызов конструктора объявлен для theOtherFred, по в объектном файле указан theFred.
Но совсем понял, что именно не понятно. Конструктор вызовется для инициализации обоих глобальных объектов theFred и theOtherFred. Для первого без параметра, для второго с параметром. В объектном файле видим, что для обоих глобальных объектов соответствующие конструкторы обозначены как «слабые» символы. Ни больше, ни меньше.
Я конкретно про «global constructors keyed to theFred». Почему тут именно theFred и только он?
Великолепная статья. Огромное спасибо и автору и переводчику!

Парочка исправлений:
по средством — посредством

Упущено слово или более на месте многоточия в разделе «Статические библиотеки»:
которая…a.o, b.o, -lx и -ly.

Хорошая статься. Автор молодец, я прочитал с удовольствием.


Еще одно исправление: LD_LIBARRY_PATH наверно имелось ввиду LD_LIBRARY_PATH
По загрузке кода немного дополню, если не возражаете на примере powerpc архитектуры,
инструментарий GNU:


файлик crt0.S который пришлёпывается к программе для начальной инициализации
имеет код для зачистки .bss, sbss и т.п., а для того-чтобы секция не загружалась,
надо в атрибутах выставлять (NOLOAD), загрузчик это видит и пропускает её,
если например вы добавили свои секции и не хотите чтобы они загружались.


        /*  clean sbss section */
    lis     5,  .LCTOC1@h
    ori     5,  5,  .LCTOC1@l

    lwz     6,  .lsbss_start(5)
    lwz     7,  .lsbss_end(5)

    cmplw       6,  7
    beq mk_clean_bss

а потом в .init пробегает по секции .ctors, где хранятся адреса
глобальных конструкторов:


static fptr_t ctors_list[1] __attribute__((section(".ctors.begin"), __used__)) = { (fptr_t) -1 };
static fptr_t ctors_end[1]  __attribute__((section(".ctors.end"),   __used__)) = { (fptr_t)  0 };

void __do_global_ctors_aux ( void )
{
    fptr_t  *fp;

    for( fp = ctors_list + 1; *fp != 0; fp++ )
        (**fp)( );
}

ну и еще конечно функция __gxx_personality для компоновки исключений в секцию .eh_frame
А при адресации в .so, насколько я помню используется секция .got (Global Offset Table), было бы
неплохо если бы вы еще статью по этому поводу написали, было бы интересно её прочесть.


Успехов!

Отличная статья. Спасибо за труд:)

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

Статические глобальные переменные видны не в файле, а в единице трансляции. То есть если статическая переменная определена в *.h файле, то при включении этого файла в другой она будет видна и там.

Так как это одна из лучших статей, что я видел, то будет правильно убрать любую неточность:)
согласен с Вашим коментарием, однако включать статическую переменную в заголовочный файл является спорным моментом. Таким образом она будет видна во всех различных единицах трансляции, где этот файл включается. Как правило это часто приводит к недопониманию факта, что переменная с одним и тем же именем будет иметь несколько копий, и как правило к ошибкам.
К сожалению, линкеры разных версий/лет и производителей (а есть еще линкеры, независимые от компиляторов), ведут себя по разному.

И стандарт С им не указ, ибо линкер в общем случае не привязан к компилятору — может например собирать Паскаль или Клиппер программы.

Статья хорошая, но возможно — требует уточнений.
Sign up to leave a comment.

Articles