Как компоновщик определяет, на какую функцию ссылаться?

0

Код ниже - упрощенная версия проблемы, которую я вижу; в основном внешняя функция testprint() завершила вызов printf() определенного в test_xprintf.cpp вместо стандартного printf().

(Да, код выглядит странно, но он предназначен для представления проблемы, поэтому он не обязательно имеет смысл сам по себе).

Почему линкер ссылается на printf() определенный в test_xprintf? Является ли это ожидаемым поведением или инструментом зависимым?

//
// test_xprintf.cpp
//

#include <iostream>
#include <stdio.h>
#include <stdarg.h>
#include "test_dbgprintf.h"

/**
 *
 * There are 3 files in total:
 * - test_xprintf.cpp
 * - test_dbgprintf.h
 * - test_dbgprintf.cpp
 *
 * Create a static C lib from test_dbgprintf.c and link with test_xprintf.cpp
 *
 * gcc -Wall -g -c -o test_dbgprintf.o test_dbgprintf.c && 
 * ar -rcs  libtest_dbgprintf.a test_dbgprintf.o &&
 * g++ -Wall test_xprintf.cpp -L. -ltest_dbgprintf -I.
 */

extern "C" int printf(const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    vprintf(format, ap);
    va_end(ap);

    return -1;
}

int main()
{
    // testprint() is a shell function which simply calls printf. 
    // If the printf function above is called, the return value will be -1. 
    int ret = testprint(4);
    std::cout << "Ret value is " << ret << std::endl;
    return ret;
}

//
// test_dbgprintf.h
//

#ifndef TEST_DBGPRINTF_H
#define TEST_DBGPRINTF_H

#if defined (__cplusplus)
extern "C" {
#endif

int testprint(int num);

#if defined (__cplusplus)
}
#endif


#endif

//
// test_dbgprintf.c
//

#include <stdio.h>

int testprint(int num)
{
    // By right this should be calling the std printf but it is linked to the printf in test_printf.cpp instead.
    return printf("This is called from testprint %d\n", num);
}
  • 0
    Зачем вам даже пытаться такой подход в C ++? Просто используйте пространство имен или класс, чтобы уточнить, какой printf вы имеете в виду.
  • 0
    Это не по выбору, просто способ, которым код был составлен. Прямо сейчас мне больше интересно узнать, почему это происходит, а не исправить.
Показать ещё 4 комментария
Теги:
linker

4 ответа

2
Лучший ответ

Это известное поведение с GNU-компоновщиком. При разрешении символа он обнаруживает только несколько определений между.o; он будет обращаться только к библиотекам, если определение не найдено в.o; и затем он прекратит поиск после первого матча.

Это поведение по умолчанию. Вы можете переопределить его с помощью --whole-archive, хотя это может раздуть ваш итоговый модуль.

1

Хорошо, думаю, я нашел правдоподобное объяснение ситуации после прочтения этого очень полезного блога.

Это зависит от порядка привязки. Это то, что я замалчивал, но глядя на то, как я связываю библиотеку:

g++ -Wall test_xprintf.cpp -L. -ltest_dbgprintf -I.

расширяя его до двух шагов:

g++ -Wall -g -c -o test_xprintf.o test_xprintf.cpp
g++ -L. test_xprintf.o -ltest_dbgprintf -I.

Я думаю, что случилось, что линкер:

  • первый экспортированный символ printf() в test_xprintf
  • когда он столкнулся с lib test_xprintf, он обнаружил неопределенный символ printf
  • просмотрел текущий список экспортированных символов, нашел printf() и счастливо связал их вместе.

libC связан последним, я считаю, что объясняет, почему он этого не видит.

Основываясь на объяснении, я считаю, что поведение ожидается.

0

В некоторых компиляторах std-библиотека рассматривается как имеющая слабую связь. У ваших определенных символов всегда есть сильная связь, кроме определенных иначе с каким-то атрибутом или подобным, но по умолчанию они связаны "сильными".

Это означает, что любые конфликтующие символы между вашими символами definde и стандартными символами будут разрешены тем, которые вы предоставляете, которые являются "сильными".

Когда я столкнулся с этой проблемой (используя компилятор CodeWarrior ColdFire), у меня всегда появилось предупреждение о компоновщике:

Symbol _sprintf multiply defined in printf.c() and libc.a(printf.o       )
Ignoring the definition in libc.a(printf.o       )

Вероятно, вы видите подобное предупреждение.

И почему иногда требуется такая связь в std lib? Ну, во встроенных системах, возможно, некоторые функции слишком тяжелы для выполнения процессором, одним из примеров является функция printf (а не printf сама по себе, так как это всего лишь оболочка, я думаю, что это был vsprintf или аналогичный, но я не знаю, я помню, какой), который довольно длинный (иногда он съел меня почти 1/4 всей памяти), поэтому я должен был предоставить свою упрощенную версию.

Я не знаю, является ли это стандартным, или просто зависит от линкера.

  • 0
    Я не видел никаких предупреждений с использованием компоновщика GNU. С другой стороны, возможно, мне нужно передать определенные флаги.
  • 0
    Я не знаю в компоновщике GNU. Но вы можете попробовать это: stackoverflow.com/questions/13993966/…
0

Ваш дополнительный символ printf, видимый как функция C, путает компоновщик. Скорее всего, вы также должны видеть предупреждения компоновщика о многократно определенных символах.

Стандарт даже не рассматривает многозначные символы в этом смысле (не говоря уже о большей части чего-либо вне единицы перевода).

Типичный компоновщик свяжет первый соответствующий символ (в некотором смысле "первый") и выдаст предупреждения для любых соответствующих дополнений.

Подводя итог: это поведение полностью зависит от реализации компоновщика.

  • 0
    Не видел никаких предупреждений. Возможно, я не прошел в соответствующие флаги?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню