Различное поведение ошибки неопределенной ссылки в linux gcc во время связывания с объектным файлом и статической библиотекой

У меня есть два исходных кода, и я хочу связать их.

// test.c
#include <stdio.h>

void lib2();

void lib1(){
    lib2();
    return 0;
}
// main.c
#include <stdio.h>

int main() {
    return 0;
}

Я использовал gcc -c main.c и gcc -c test.c для создания файлов объектов.

$ ls *.o
main.o  test.o

и я использовал команду ar rcs test.a test.o для создания статической библиотеки (test.a) из объектного файла test.o

Затем я попытался создать исполняемый файл, связав main.o с test.a или test.o. Насколько мне известно, файл статической библиотеки (расширение .a) представляет собой своего рода простую коллекцию объектных файлов (.o). поэтому я ожидал, что оба дадут одинаковый результат: ошибка или успех. но это не так.

Связывание с объектным файлом дает ошибку undefined reference.

$ gcc -o main main.o test.o
/usr/bin/ld: test.o: in function `lib1':
test.c:(.text+0xe): undefined reference to `lib2'
collect2: error: ld returned 1 exit status
$

но связывание со статической библиотекой не дает никаких ошибок и успеха при компиляции.

$ gcc -o main main.o test.a
$

Почему это происходит? и как я могу получить ошибки undefined reference даже при линковке со статическими библиотеками?

Попробуйте вызвать lib1() из main() (ошибка все равно не гарантирована, но с большей вероятностью)

M.M 09.02.2023 01:19

Да, вызывая/ссылаясь на lib1() из main.c, я могу получить ошибку неопределенной ссылки либо для объектного файла, либо для статической библиотеки. но я хочу получить целые неопределенные ссылочные ошибки, даже если они на самом деле не указаны.

rumblenimble 09.02.2023 01:36

объекты и библиотеки обрабатываются компоновщиком по-разному.

0___________ 09.02.2023 01:41

Решение: соединение с --whole-archive. например ld --whole-archive -o main main.o test.a или gcc -o main -Wl,--whole-archive test.a main.o -Wl,--no-whole-archive

rumblenimble 09.02.2023 04:49
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
67
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Если ваш код содержит выражение вызова функции, то стандарт языка требует наличия определения функции. (См. C11 6.9/3). Если вы не предоставите определение, то это неопределенное поведение, не требующее диагностики.

Правило было написано таким образом, чтобы поставщики реализации не были вынуждены выполнять анализ, чтобы определить, вызывается ли когда-либо функция или нет; например, в вашем библиотечном сценарии компилятор не вынужден копаться в библиотеке, если ни один из остального кода не содержит ничего, что ссылается на эту библиотеку.

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

Возможно, вы сможете изменить поведение в первом случае, используя параметры компоновщика, такие как удаление неиспользуемых разделов кода. Еще одна вещь, которую вы можете сделать, это вызвать lib1() из main() — это все еще не гарантирует, что приведет к ошибке, но более вероятно.

Обоснование другое. Представьте себе библиотеку, в которой некоторым функциям требуются символы из объектных файлов (например, функции обратного вызова). Если бы компоновщик анализировал любой объект в библиотеке, ваша программа должна была бы иметь все эти объекты и функции, определенные, даже если вы их не используете.

0___________ 09.02.2023 01:43

@0___________ Я не понимаю, чем это отличается от того, что я сказал

M.M 09.02.2023 01:45

Понял. Как вы сказали, это зависит от реализации, и, как сказал @0___________, «объекты и библиотеки обрабатываются компоновщиком по-разному», кажется, в моем случае компоновщик решает вызвать ошибку неопределенной ссылки после того, как передан объектный файл, но не статическая библиотека. Чтобы найти все неопределенные ссылки, я должен извлечь все объектные файлы из статической библиотеки и связать их с ними или использовать другие методы, такие как команда nm. Спасибо за Ваш ответ!

rumblenimble 09.02.2023 02:15

Этот ответ на самом деле не отвечает на вопрос «почему .o отличается от .a».

Employed Russian 09.02.2023 14:45

@EmployedRussian это «потому что реализация работает по-другому»

M.M 09.02.2023 22:00

Заставьте компоновщик выполнить некоторую работу, используя опцию -flto, и ошибка исчезнет.

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

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

Andrew Henle 09.02.2023 02:35

Я ожидал, что оба дадут одинаковый результат: ошибка или успех. но это не так.

Ваше ожидание неверно. Хорошее объяснение разницы между .o и .a в отношении ссылок — здесь.

Другие вопросы по теме