Несколько общих библиотечных конструкторов не вызывается - PullRequest
1 голос
/ 08 января 2020

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

Вот минимальный пример:

foo.h

#pragma once
void foo();

foo. c

#include <stdio.h>
#include "foo.h"

void foo() {};

void __attribute__((constructor)) init()
{
    printf("foo init\n");
}

бар.ч

#pragma once
void bar();

бар. c

#include <stdio.h>
#include "bar.h"

void bar() {};

void __attribute__((constructor)) init()
{
    printf("bar init\n");
}

основной. c

#include <stdio.h>
#include "foo.h"
#include "bar.h"

int main()
{
    foo();
    bar();
    return 0;
}

Компиляция:

gcc -o libfoo.so foo.c -fPIC -shared
gcc -o libbar.so bar.c -fPIC -shared
gcc -o main main.c -lfoo -lbar -L.

Теперь, если я запускаю main, я получаю:

foo init
foo init

Я ожидаю увидеть один foo init и один bar init при вызове конструктора каждой библиотеки, но вместо этого конструктор foo вызывается дважды. Также интересно то, что если я поменяю порядок библиотек на последнем этапе компиляции на

gcc -o main main.c -lbar -lfoo -L.

Вывод, который я получу, будет

bar init
bar init

Я могу исправить это, изменив одно из следующих значений: имена функций конструктора, чтобы сделать их уникальными. Я также должен отметить, что это происходит независимо от имени функции, при условии, что они названы одинаково в обеих библиотеках.

Мой вопрос: почему это происходит в первую очередь? Я предполагаю, что это происходит из-за конфликта имен, но я точно не понимаю, что происходит.

Я тестировал, используя g cc 4.8.4 в Ubuntu 14.04.5 LTS, а также g cc 7.4.0 в Ubuntu 18.04.2 LTS в WSL.

1 Ответ

4 голосов
/ 08 января 2020

Это нормально и ожидаемо. В языке C каждый внешний идентификатор может иметь не более одного определения; если их больше, поведение не определено. Связывание ELF dynamici c (довольно кропотливо) предназначено для обеспечения поведения, максимально приближенного к статическому c связыванию, где, несмотря на то, что более чем одна библиотека определяет внешний идентификатор с одинаковым именем, поведение вполне может быть четко определено из-за использования только одного определения (библиотеки stati c - это просто архивы объектных файлов, в которые вставляются только объектные файлы, необходимые для предоставления определений для неопределенных ссылок на символы). Это означает, что первое определение (в порядке ссылки) используется всякий раз, когда определения существуют в более чем одной связанной общей библиотеке.

Это объяснение того, что вы видите, но решение очень проще. Для конструкторов нет причин иметь внешнюю связь для начала. Сделайте их всех static, и неважно, как их зовут. А для внешних идентификаторов в ваших библиотеках, которым нужно , чтобы быть внешними, убедитесь, что они названы в соответствии с неким шаблоном, где они не будут sh.

...