Dlaczego musisz połączyć bibliotekę matematyczną w C?


254

Gdybym to <stdlib.h>czy <stdio.h>w programie C nie mam połączyć je podczas kompilacji, ale muszę się linkiem do <math.h>korzystając -lmz gcc, na przykład:

gcc test.c -o test -lm

Jaki jest tego powód? Dlaczego muszę jawnie łączyć bibliotekę matematyczną, ale nie inne biblioteki?

Odpowiedzi:


249

Funkcje w stdlib.hi stdio.hmają implementacje w libc.so(lub libc.ado łączenia statycznego), które są domyślnie połączone z plikiem wykonywalnym (tak jakby -lczostały określone). GCC można poinstruować, aby unikał tego automatycznego połączenia z opcjami -nostdliblub -nodefaultlibs.

Funkcje matematyczne math.hmają implementacje w libm.so(lub libm.ado łączenia statycznego) i libmdomyślnie nie są połączone. Istnieją historyczne powody tego libm/ libcpodziału, żaden z nich nie jest zbyt przekonujący.

Co ciekawe, środowisko wykonawcze C ++ libstdc++wymaga libm, więc jeśli skompilujesz program C ++ za pomocą GCC ( g++), automatycznie zostaniesz libmpołączony.


8
Nie ma to nic wspólnego z Linuksem, ponieważ było powszechne na długo przed Linuksem. Podejrzewam, że ma to coś wspólnego z próbą zminimalizowania rozmiaru pliku wykonywalnego, ponieważ istnieje wiele programów, które nie potrzebują funkcji matematycznych.
David Thornley,

39
W starożytnych systemach, gdyby funkcje matematyczne były zawarte w libc, kompilacja wszystkich programów byłaby wolniejsza, wyjściowe pliki wykonywalne byłyby większe, a środowisko wykonawcze wymagałoby więcej pamięci, bez korzyści dla większości programów, które w ogóle nie używają tych funkcji matematycznych. Obecnie mamy dobre wsparcie dla bibliotek współdzielonych, a nawet przy statycznym łączeniu, biblioteki standardowe są skonfigurowane tak, aby nieużywany kod mógł zostać odrzucony, więc żadna z nich nie jest już dobrym powodem.
ephemient

38
@ephemient Nawet w dawnych czasach łącze do biblioteki nie ściągało całej zawartości biblioteki do pliku wykonywalnego. Linkery, choć często ignorowana technologia, historycznie były dość skuteczne.

7
@ephemient Ponadto, biblioteki współdzielone są dostępne dłużej, niż mogłoby się wydawać. Zostały one wynalezione w latach 50., a nie w latach 80.

5
Przypuszczam, że pod koniec dnia patrzymy na konserwatyzm GCC: „zawsze tak było”. Żałuję tylko, że nie zastosowali tego samego rozumowania do rozszerzeń kompilatora.

77

Pamiętaj, że C jest starym językiem i że FPU są stosunkowo nowym zjawiskiem. Po raz pierwszy zobaczyłem C na 8-bitowych procesorach, w których dużo pracy zajmowała nawet arytmetyka liczb całkowitych 32-bitowych. Wiele z tych wdrożeń nawet nie mieć pływający punkt biblioteka matematyczna dostępny!

Nawet na pierwszych 68000 maszynach (Mac, Atari ST, Amiga) koprocesory zmiennoprzecinkowe były często drogimi dodatkami.

Aby wykonać całą tę matematykę zmiennoprzecinkową, potrzebna była dość spora biblioteka. A matematyka będzie wolna. Więc rzadko używałeś pływaków. Próbowałeś zrobić wszystko za pomocą liczb całkowitych lub skalowanych liczb całkowitych. Kiedy musiałeś uwzględnić matematykę, zgrzytałeś zębami. Często piszesz własne przybliżenia i tabele wyszukiwania, aby tego uniknąć.

Kompromisy istniały przez długi czas. Czasami istniały konkurencyjne pakiety matematyczne o nazwie „fastmath” lub podobne. Jakie jest najlepsze rozwiązanie matematyczne? Naprawdę dokładne, ale powolne rzeczy? Niedokładne, ale szybkie? Duże tabele dla funkcji trig? Większość implementacji stała się oczywista dopiero po zagwarantowaniu, że koprocesory znajdują się w komputerze. Wyobrażam sobie, że jest gdzieś jakiś programista, pracujący na wbudowanym układzie scalonym, próbujący zdecydować, czy wprowadzić bibliotekę matematyczną, aby poradzić sobie z jakimś problemem matematycznym.

Właśnie dlatego matematyka nie była standardem . Wiele, a może większość programów nie używało pojedynczej liczby zmiennoprzecinkowej. Gdyby FPU zawsze były w pobliżu, a operacje zmiennoprzecinkowe i podwajania zawsze były tanie w obsłudze, bez wątpienia byłby to „standard”.


Heh, używam przybliżeń Pade dla (1 + x) ^ y w Javie, na komputerze stacjonarnym. Log, exp i pow są nadal wolne.
quant_dev

Słuszna uwaga. I widziałem przybliżenia sin () we wtyczkach audio.
Nosredna

11
To wyjaśnia, dlaczego libmdomyślnie nie jest połączone, ale matematyka była standardowa od C89, a wcześniej K&R de facto ją ustandaryzował, więc twoja uwaga „stdmath” nie ma sensu.
Fred Foo

@FredFoo Typy i interfejsy zostały znormalizowane, ale nie implementacje. Myślę, że Nosredna odnosi się do standardowej biblioteki matematycznej.
Tim Bird

72

Z powodu absurdalnej praktyki historycznej, której nikt nie chce naprawić. Skonsolidowanie wszystkich funkcji wymaganych przez C i POSIX w jednym pliku biblioteki nie tylko pozwoliłoby uniknąć wielokrotnego zadawania tego pytania, ale także zaoszczędziłoby znaczną ilość czasu i pamięci podczas dynamicznego łączenia, ponieważ każdy .sopołączony plik wymaga operacji systemu plików zlokalizować i znaleźć, a także kilka stron dla jego zmiennych statycznych, relokacji itp.

Implementacja gdzie wszystkie funkcje są w jednej biblioteki i -lm, -lpthread, -lrt, itd opcje są wszystkie no-ops (lub link do pustych .aplików) jest idealnie zgodnym z POSIX i na pewno korzystne.

Uwaga: mówię o POSIX, ponieważ samo C nie określa niczego, jak wywoływany jest kompilator. W ten sposób można traktować gcc -std=c99 -lmjako sposób specyficzny dla implementacji, że kompilator musi być wywoływany w celu zachowania zgodności.


9
+1 za wskazanie, że POSIX nie wymaga istnienia oddzielnych bibliotek libm, libc i librt. Na przykład w systemie Mac OS wszystko znajduje się w jednym systemie libSystem (który obejmuje również libdbm, libdl, libgcc_s, libinfo, libm, libpoll, libproc i librpcsvc).
F'x

3
–1 za spekulowanie na temat wpływu wyszukiwania biblioteki na wydajność bez tworzenia kopii zapasowej linkiem lub cyframi. „Profil. Nie spekuluj”
F'x

12
To nie jest spekulacja. Nie mam żadnych opublikowanych artykułów, ale sam wykonałem wszystkie pomiary, a różnica jest ogromna. Wystarczy użyć stracejednej z opcji pomiaru czasu, aby zobaczyć, ile czasu startowego spędza się na łączeniu dynamicznym, lub porównać działanie ./configurew systemie, w którym wszystkie standardowe narzędzia są połączone statycznie z tymi, w których są one połączone dynamicznie. Nawet główni twórcy aplikacji komputerowych i integratorzy systemów są świadomi kosztów dynamicznego łączenia; dlatego istnieją rzeczy takie jak prelink. Jestem pewien, że możesz znaleźć wzorce w niektórych z tych artykułów.
R .. GitHub ZATRZYMAJ POMOC W LODZIE

1
Zauważ, że POSIX nie wymagają -lm, aby być przyjęte i aplikacji, które wykorzystują interfejsy matematycznych musi używać -lm, ale może to być opcja wewnętrzny obchodzić (lub nawet ignorowane) przez polecenie kompilatora, a nie rzeczywiste plik biblioteki. Lub może to być pusty .aplik, jeśli interfejsy znajdują się w głównym libc.
R .. GitHub ZATRZYMAJ LÓD

6
@FX: Nie wiem, dlaczego wcześniej o tym zapomniałem: strace -ttłatwo pokażę ci czas poświęcony na dynamiczne linkowanie. To nie jest ładne. A w Linuksie inspekcja /proc/sys/smapspokaże ci narzut pamięci dodatkowych bibliotek.
R .. GitHub ZATRZYMAJ LÓD

33

Ponieważ time()i niektóre inne funkcje są builtinzdefiniowane w samej bibliotece C ( libc), a GCC zawsze prowadzi do libc, chyba że użyjesz -ffreestandingopcji kompilacji. Jednak żyją funkcje matematyczne, w libmktórych gcc nie jest pośrednio powiązany.


8
Na LLVM gcc nie muszę dodawać -lm. Dlaczego to?
bot47

26

Wyjaśnienie podano tutaj :

Więc jeśli twój program korzysta z funkcji matematycznych i włącznie math.h, musisz jawnie połączyć bibliotekę matematyczną, przekazując -lmflagę. Powodem tego szczególnego rozdzielenia jest to, że matematycy są bardzo wybredni w zakresie sposobu obliczania ich matematyki i mogą chcieć użyć własnej implementacji funkcji matematycznych zamiast standardowej implementacji. Gdyby funkcje matematyczne były skupione, libc.anie byłoby to możliwe.

[Edytować]

Nie jestem jednak pewien, czy się z tym zgadzam. Jeśli masz bibliotekę, która zapewnia, powiedzmy, sqrt()i przekazujesz ją przed biblioteką standardową, linker uniksowy przejmie twoją wersję, prawda?


10
Nie sądzę, że istnieje gwarancja, że ​​tak się stanie; zamiast tego może dojść do konfliktu symboli. Prawdopodobnie zależy to od linkera i układu biblioteki. Wciąż uważam ten powód za słaby; jeśli tworzysz niestandardową funkcję sqrt, naprawdę nie powinieneś nadawać jej takiej samej nazwy jak standardowa funkcja sqrt, nawet jeśli robi to samo ...
efhemient

1
Rzeczywiście, utworzenie własnej funkcji (niestatycznej) o nazwie sqrtpowoduje, że program ma niezdefiniowane zachowanie.
R .. GitHub ZATRZYMAJ POMOC W LODZIE

@Bastien Dobre znalezisko. I dochodząc do sedna, co rozumiesz przez „przed standardową biblioteką”? Pomyślałem, że standardowa biblioteka jest domyślnie połączona i nie wymaga połączenia za pomocą opcji wiersza poleceń. Tak więc standardowa biblioteka będzie pierwszym krokiem do linkera i nie można umieścić własnej implementacji „przed standardową biblioteką”.
Rocky Inde,

@RockyInde: spójrz na moją odpowiedź, myślę, że właściwie miałem na myśli „przed standardową biblioteką matematyczną”. Ale myślę, że istnieją opcje kompilatora, aby nie łączyć standardowej biblioteki C, co pozwoliłoby ci przekazać twoją.
Bastien Léonard,

@ BastienLéonard Używam gcc w wersji 7.2, która -lmjest całkowicie opcjonalna. Wszelkie pomysły
Donghua Liu

5

Dokładna dyskusja na temat linkowania do bibliotek zewnętrznych znajduje się we wstępie do GCC - Linkowanie do bibliotek zewnętrznych . Jeśli biblioteka jest członkiem standardowych bibliotek (takich jak stdio), nie trzeba określać w kompilatorze (tak naprawdę linkerze), aby je połączyć.

EDYCJA: Po przeczytaniu niektórych innych odpowiedzi i komentarzy, myślę, że odwołanie do libc.a i odwołanie do libm, które łączy do obu, mają wiele do powiedzenia na temat tego, dlaczego oba są oddzielne.

Zauważ, że wiele funkcji w „libm.a” (biblioteka matematyczna) jest zdefiniowanych w „math.h”, ale nie ma ich w libc.a. Niektóre z nich mogą być mylące, ale ogólna zasada jest taka - biblioteka C zawiera funkcje, które ANSI nakazuje istnieć, więc nie potrzebujesz -lm, jeśli używasz tylko funkcji ANSI. W przeciwieństwie do tego, `libm.a 'zawiera więcej funkcji i obsługuje dodatkowe funkcje, takie jak oddzwanianie matherr i zgodność z kilkoma alternatywnymi standardami zachowania w przypadku błędów FP. Zobacz sekcję libm, aby uzyskać więcej informacji.


1
Co nie odpowiada na pytanie, dlaczego trzeba linkować w bibliotekach meczów osobno. Oczywiście chcesz osobno połączyć biblioteki OpenGL, ale prawdopodobnie biblioteki matematyczne są ogólnie przydatne.
David Thornley,

@David: Masz rację. Pytanie nie było dla mnie jasne, o to właśnie pytał OP. Edytowałem moją odpowiedź, tak jak skomentowałeś.
Bill the Lizard

Znam powód, dla którego skompilowałem program, który korzysta z tej sqrtfunkcji i działa bez włączania biblioteki przez -lm. Dzięki!
L_K

5

Jak powiedział ephemient, biblioteka libc w C jest domyślnie połączona i ta biblioteka zawiera implementacje stdlib.h, stdio.h i kilka innych standardowych plików nagłówkowych. Aby dodać do tego, zgodnie z „ An Introduction to GCC ”, polecenie linkera dla podstawowego programu „Hello World” w C wygląda następująco:

ld -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o 
/usr/lib/crti.o /usr/libgcc-lib /i686/3.3.1/crtbegin.o
-L/usr/lib/gcc-lib/i686/3.3.1 hello.o -lgcc -lgcc_eh -lc 
-lgcc -lgcc_eh /usr/lib/gcc-lib/i686/3.3.1/crtend.o /usr/lib/crtn.o

Zwróć uwagę na opcję -lc w trzecim wierszu, który łączy bibliotekę C.


3

Myślę, że to trochę arbitralne. Musisz gdzieś narysować linię (które biblioteki są domyślne, a które należy określić).

Daje to możliwość zastąpienia go innym, który ma te same funkcje, ale nie sądzę, że jest to bardzo powszechne.

EDYCJA: (z moich własnych komentarzy): Myślę, że gcc robi to, aby zachować kompatybilność wsteczną z oryginalnym cc. Domyślam się, dlaczego cc robi to z powodu czasu kompilacji - cc zostało napisane dla maszyn o znacznie mniejszej mocy niż obecnie. Wiele programów nie ma matematyki zmiennoprzecinkowej i prawdopodobnie wzięły każdą bibliotekę, która nie była często używana jako domyślna. Zgaduję, że siłą napędową był czas kompilacji systemu operacyjnego UNIX i towarzyszące mu narzędzia.


myślę, że mentalność tego pytania polega na tym, że zawartość libm jest w dużej mierze częścią standardowej biblioteki C, dlaczego nie są w libc?
Evan Teran

1
Powodem dla gcc jest utrzymanie zgodności z oryginalnym cc w AT&T Unix. Użyłem 3B2 w 1988 roku i musiałeś -lm, aby uzyskać matematykę. Wówczas wydawało mi się to całkowicie arbitralne. W Visual Studio nie pamiętam, aby kiedykolwiek dodawać matematykę, ale czasami trzeba dodawać inne biblioteki pozornie c-runtime. Zakładam, że dostawcy kompilatora mają powód (czas kompilacji?), Ale teraz założę się, że gcc próbuje po prostu być kompatybilnym wstecz.
Lou Franco

3

Jeśli wstawię stdlib.h lub stdio.h, nie muszę ich łączyć, ale muszę je łączyć podczas kompilacji:

stdlib.h, stdio.hto pliki nagłówkowe. Uwzględniasz je dla swojej wygody. Prognozują tylko, jakie symbole staną się dostępne, jeśli umieścisz link w odpowiedniej bibliotece. Implementacje znajdują się w plikach biblioteki, tam właśnie naprawdę działają te funkcje.

Uwzględnienie math.hto tylko pierwszy krok do uzyskania dostępu do wszystkich funkcji matematycznych.

Ponadto nie musisz tworzyć odnośników, libmjeśli nie używasz jego funkcji, nawet jeśli robisz to, #include <math.h>co jest tylko informacyjnym krokiem dla kompilatora o symbolach.

stdlib.h, stdio.hodnoszą się do funkcji dostępnych w libc, które są zawsze połączone, aby użytkownik nie musiał tego robić sam.


2

stdio jest częścią standardowej biblioteki C, z którą domyślnie łączy się gcc.

Implementacje funkcji matematycznych znajdują się w osobnym pliku libm, do którego domyślnie nie jest podłączony, więc należy go podać -lm. Nawiasem mówiąc, nie ma związku między tymi plikami nagłówkowymi a plikami bibliotek.


3
wie o tym ... pyta dlaczego
Evan Teran

Mówi dlaczego. Simon wyjaśnia, że ​​niektóre biblioteki są domyślnie połączone, na przykład stdio, natomiast biblioteka matematyczna nie jest domyślnie połączona, dlatego należy ją określić.
mnuzzo

5
Powiedziałbym, że charakter pytania polega na pytaniu, dlaczego libm nie jest domyślnie połączony (lub nawet oddzielony od libc), ponieważ jego zawartość jest w dużej mierze częścią standardowej biblioteki c.
Evan Teran

2

Ja przypuszczam , że jest to sposób, aby aplikacje, które nie używać go w ogóle wykonać nieco lepiej. Oto moje przemyślenie na ten temat.

Systemy operacyjne x86 (i wyobrażam sobie inne) muszą przechowywać stan FPU na przełączniku kontekstu. Jednak większość systemów operacyjnych zadaje sobie trud tylko zapisać / przywrócić ten stan po pierwszej próbie użycia FPU przez aplikację.

Oprócz tego w bibliotece matematycznej znajduje się prawdopodobnie jakiś podstawowy kod, który ustawi FPU na zdrowy stan podstawowy po załadowaniu biblioteki.

Tak więc, jeśli w ogóle nie użyjesz żadnego kodu matematycznego, nic takiego się nie wydarzy, dlatego system operacyjny nie musi w ogóle zapisywać / przywracać żadnego stanu FPU, dzięki czemu przełączanie kontekstu jest nieco bardziej wydajne.

Tylko zgadnij.

EDYTOWAĆ: w odpowiedzi na niektóre komentarze ta sama podstawowa przesłanka nadal ma zastosowanie w przypadkach innych niż FPU (założeniem jest, że aplikacje, które nie wykorzystywały libm, działają nieco lepiej).

Na przykład, jeśli istnieje soft-FPU, który był podobny we wczesnych dniach C. Wtedy oddzielne libm może zapobiec niepotrzebnemu łączeniu się dużego (i powolnego, jeśli był użyty) kodu.

Ponadto, jeśli dostępne jest tylko statyczne łączenie, stosuje się podobny argument, że utrzyma on rozmiary plików wykonywalnych i skróci czas kompilacji.


Jeśli nie łączysz się z libm, ale dotykasz FPU x87 innymi sposobami (na przykład operacje na liczbach zmiennoprzecinkowych), jądro x86 musi zapisać stan FPU. Nie sądzę, żeby to było bardzo dobre przypuszczenie ...
ephemient

oczywiście jeśli ręcznie użyjesz FPU, jądro nadal będzie musiało zapisać / przywrócić swój stan. Mówiłem, że jeśli nigdy go nie użyjesz (w tym nie użyjesz libm), to nie będzie to konieczne.
Evan Teran

Naprawdę może bardzo bardzo zależeć od jądra. Biblioteka matematyczna używana przez jądro może mieć funkcję save_FPU_on_switch (), która ją włącza, podczas gdy inne wykrywają tylko dotknięcie FPU.
Earlz

1
Jeśli dobrze pamiętam, cały problem długo wyprzedza koprocesory zmiennoprzecinkowe, nawet będąc na mikroprocesorach.
Nosredna

@earlz: podejście polegające na zapisywaniu żądań w bibliotece matematycznej byłoby okropnym projektem. Co jeśli wykorzystają FPU w inny sposób? Jedynym rozsądnym podejściem (oprócz po prostu zawsze zapisywania / przywracania) byłoby wykrycie użycia, a następnie rozpoczęcie zapisywania / przywracania.
Evan Teran
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.