Perché devi collegare la libreria matematica in C?


254

Se includo <stdlib.h>o <stdio.h>in un programma C non devo collegarli durante la compilazione ma devo collegarmi <math.h>, usando -lmcon gcc, ad esempio:

gcc test.c -o test -lm

Qual è la ragione di ciò? Perché devo collegare esplicitamente la libreria matematica ma non le altre librerie?

Risposte:


249

Le funzioni sono stdlib.he stdio.hhanno implementazioni in libc.so(o libc.aper collegamento statico), che è collegato al tuo eseguibile per impostazione predefinita (come se -lcfosse specificato). GCC può essere incaricato di evitare questo collegamento automatico con le opzioni -nostdlibo -nodefaultlibs.

Le funzioni matematiche math.hhanno implementazioni in libm.so(o libm.aper collegamento statico) e libmnon sono collegate per impostazione predefinita. Ci sono ragioni storiche per questo libm/ libcscissione, nessuna delle quali molto convincente.

È interessante notare che il runtime C ++ libstdc++richiede libm, quindi se si compila un programma C ++ con GCC ( g++), si verrà automaticamente libmcollegati.


8
Questo non ha nulla a che fare con Linux, poiché era comune molto prima di Linux. Ho il sospetto che abbia qualcosa a che fare con il tentativo di ridurre al minimo le dimensioni dell'eseguibile, dal momento che ci sono molti programmi che non richiedono funzioni matematiche.
David Thornley,

39
Sui sistemi antichi, se le funzioni matematiche fossero contenute in libc, la compilazione di tutti i programmi sarebbe più lenta, gli eseguibili di output sarebbero più grandi e il runtime richiederebbe più memoria, senza alcun vantaggio per la maggior parte dei programmi che non usano affatto queste funzioni matematiche. In questi giorni abbiamo un buon supporto per le librerie condivise e anche quando si collegano staticamente, le librerie standard sono impostate in modo che il codice inutilizzato possa essere scartato, quindi nessuna di queste sono più buone ragioni.
effimero

38
@ephemient Anche ai vecchi tempi, il collegamento a una libreria non estraeva tutti i contenuti della libreria nell'eseguibile. I linker, sebbene una tecnologia spesso ignorata, sono stati storicamente abbastanza efficaci.

7
@ephemient Inoltre, le librerie condivise sono in circolazione da più tempo di quanto si possa pensare. Sono stati inventati negli anni '50, non negli anni '80.

5
Suppongo che in fin dei conti ciò che stiamo guardando non sia altro che il conservatorismo del GCC: "ha sempre funzionato così". Vorrei solo che applicassero lo stesso ragionamento alle estensioni del compilatore.

77

Ricorda che C è un linguaggio antico e che le FPU sono un fenomeno relativamente recente. Ho visto per la prima volta C su processori a 8 bit in cui era molto difficile fare anche l'aritmetica a numeri interi a 32 bit. Molte di queste implementazioni non ha nemmeno hanno una libreria matematica in virgola mobile a disposizione!

Anche sulle prime 68000 macchine (Mac, Atari ST, Amiga), i coprocessori a virgola mobile erano spesso componenti aggiuntivi costosi.

Per fare tutta quella matematica in virgola mobile, avevi bisogno di una libreria abbastanza grande. E la matematica sarebbe stata lenta. Quindi raramente hai usato i galleggianti. Hai provato a fare tutto con numeri interi o interi in scala. Quando hai dovuto includere math.h, hai stretto i denti. Spesso, scrivevi le tue approssimazioni e le tue tabelle di ricerca per evitarlo.

I compromessi esistevano da molto tempo. A volte c'erano pacchetti di matematica concorrenti chiamati "fastmath" o simili. Qual è la migliore soluzione per la matematica? Roba davvero accurata ma lenta? Inesatto ma veloce? Grandi tavoli per le funzioni di trigger? Non è stato garantito fino a quando i coprocessori erano nel computer che la maggior parte delle implementazioni è diventata ovvia. Immagino che ci sia qualche programmatore là fuori da qualche parte in questo momento, che lavora su un chip incorporato, cercando di decidere se portare nella libreria matematica per gestire qualche problema matematico.

Ecco perché la matematica non era standard . Molti o forse la maggior parte dei programmi non utilizzava un singolo float. Se le FPU fossero sempre state presenti e galleggiassero e raddoppiassero fossero sempre a buon mercato per operare, senza dubbio ci sarebbe stato uno "stdmath".


Eh, sto usando approssimativi Pade per (1 + x) ^ y in Java, in un PC desktop. Log, exp e pow sono ancora lenti.
quant_dev,

Buon punto. E ho visto approssimazioni per sin () nei plugin audio.
Nosredna,

11
Questo spiega perché libmnon è collegato per impostazione predefinita, ma la matematica era standard da C89 e prima di allora, K&R lo aveva di fatto standardizzato, quindi la tua osservazione "stdmath" non ha senso.
Fred Foo,

@FredFoo I tipi e le interfacce erano standardizzati, ma non le implementazioni. Penso che Nosredna si riferisca a una libreria matematica standard.
Tim Bird

72

A causa della ridicola pratica storica che nessuno è disposto a risolvere. Il consolidamento di tutte le funzioni richieste da C e POSIX in un singolo file di libreria non solo eviterebbe di porre ripetutamente questa domanda, ma risparmierebbe anche una quantità significativa di tempo e memoria durante il collegamento dinamico, poiché ogni .sofile collegato richiede le operazioni del filesystem per localizzarlo e trovarlo, e alcune pagine per le sue variabili statiche, rilocazioni, ecc.

Un'implementazione in cui tutte le funzioni sono in una biblioteca e la -lm, -lpthread, -lrt, ecc opzioni sono tutti no-ops (o linkare a vuote .afile) è perfettamente conforme a POSIX e certamente preferibile.

Nota: sto parlando di POSIX perché C stesso non specifica nulla su come viene invocato il compilatore. Quindi puoi semplicemente considerare gcc -std=c99 -lmcome il modo specifico di implementazione che il compilatore deve essere invocato per un comportamento conforme.


9
+1 per indicare che POSIX non richiede l'esistenza di librerie libm, libc e librt separate. Ad esempio, su Mac OS tutto si trova in un singolo libSystem (che include anche libdbm, libdl, libgcc_s, libinfo, libm, libpoll, libproc e librpcsvc).
F'x,

3
–1 per speculare sull'impatto della ricerca nella libreria sulle prestazioni senza eseguire il backup con un collegamento o numeri. "Profilo. Non speculare"
F'x,

12
Questa non è speculazione. Non ho articoli pubblicati, ma ho fatto tutte le misurazioni da solo e la differenza è enorme. Basta usare stracecon una delle opzioni di temporizzazione per vedere quanto tempo di avvio viene impiegato per il collegamento dinamico o confrontare l'esecuzione ./configuresu un sistema in cui tutte le utilità standard sono collegate staticamente rispetto a una in cui sono collegate dinamiche. Anche i principali sviluppatori di app desktop e integratori di sistemi sono consapevoli dei costi del collegamento dinamico; ecco perché esistono cose come il prelink. Sono sicuro che puoi trovare benchmark in alcuni di questi documenti.
R .. GitHub smette di aiutare ICE il

1
Si noti che POSIX non richiede -lmdi essere accettato e le applicazioni che utilizzano le interfacce di matematica deve utilizzare -lm, ma può essere un'opzione interna gestita (o addirittura ignorato) da parte del comando del compilatore, non un vero e proprio file di libreria. Oppure può essere solo un .afile vuoto se le interfacce sono nella libreria principale.
R .. GitHub smette di aiutare ICE il

6
@FX: non so perché ho dimenticato di menzionarlo prima: strace -ttti mostrerò facilmente il tempo dedicato al collegamento dinamico. Non è carino. E su Linux, il controllo /proc/sys/smapsti mostrerà il sovraccarico di memoria di librerie aggiuntive.
R .. GitHub FERMA AIUTANDO ICE

33

Perché time()e alcune altre funzioni sono builtindefinite nella libreria C ( libc) stessa e GCC si collega sempre a libc a meno che non si usi l' -ffreestandingopzione di compilazione. Tuttavia vivono funzioni matematiche in libmcui non è implicitamente collegato da gcc.


8
Su LLVM gcc non devo aggiungere -lm. Perchè è questo?
bot47,

26

Una spiegazione è data qui :

Quindi, se il tuo programma utilizza funzioni matematiche e incluse math.h, devi collegare esplicitamente la libreria matematica passando la -lmbandiera. Il motivo di questa particolare separazione è che i matematici sono molto esigenti riguardo al modo in cui viene calcolata la loro matematica e potrebbero voler usare la propria implementazione delle funzioni matematiche anziché l'implementazione standard. Se le funzioni matematiche fossero raggruppate libc.a, non sarebbe possibile farlo.

[Modificare]

Non sono sicuro di essere d'accordo con questo, però. Se hai una libreria che fornisce, diciamo, sqrt()e la passi davanti alla libreria standard, un linker Unix prenderà la tua versione, giusto?


10
Non credo che ci sia una garanzia che ciò accada; potresti invece finire con un conflitto di simboli. Probabilmente dipenderebbe dal linker e dal layout della libreria. Trovo ancora che quella ragione sia debole; se stai creando una funzione sqrt personalizzata, non dovresti davvero darle lo stesso nome della funzione sqrt standard, anche se fa la stessa cosa ...
effimero

1
In effetti, la creazione della propria funzione (non statica) sqrtporta a un programma con un comportamento indefinito.
R .. GitHub FERMA AIUTANDO ICE

@Bastien Buona scoperta. E arrivando al tuo punto, cosa intendi con "prima della libreria standard"? Ho pensato, la libreria standard è collegata per impostazione predefinita e non è necessario che sia collegata tramite le opzioni della riga di comando. Pertanto, la libreria standard sarà la prima scelta per il linker e non sarà possibile implementare la propria implementazione "prima della libreria standard".
Rocky Inde,

@ RockyInde: guarda la mia risposta, penso che in realtà intendessi "prima della libreria matematica standard". Ma penso che ci siano opzioni di compilatore per non collegare la libreria C standard, che ti permetterebbe di passare la tua.
Bastien Léonard,

@ BastienLéonard Uso gcc della versione 7.2, che -lmè totalmente opzionale. Qualche idea
Donghua Liu,

5

C'è una discussione approfondita sul collegamento a librerie esterne in An Introduction to GCC - Collegamento con librerie esterne . Se una libreria è un membro delle librerie standard (come stdio), non è necessario specificare al compilatore (in realtà il linker) per collegarle.

EDIT: Dopo aver letto alcune delle altre risposte e commenti, penso che il riferimento libc.a e il riferimento libm a cui si collega entrambi hanno molto da dire sul perché i due sono separati.

Nota che molte delle funzioni in 'libm.a' (la libreria matematica) sono definite in 'math.h' ma non sono presenti in libc.a. Alcuni possono essere fonte di confusione, ma la regola empirica è questa: la libreria C contiene le funzioni che devono essere stabilite da ANSI, quindi non è necessario il -lm se si utilizzano solo funzioni ANSI. Al contrario, `libm.a 'contiene più funzioni e supporta funzionalità aggiuntive come il call-back di matherr e la conformità a diversi standard di comportamento alternativi in ​​caso di errori FP. Vedi la sezione libm, per maggiori dettagli.


1
Il che non risponde alla domanda sul perché devi collegarti separatamente nelle librerie delle partite. Ovviamente devi collegare separatamente le librerie OpenGL, ma probabilmente le librerie matematiche sono generalmente utili.
David Thornley,

@ David: Hai ragione. Non mi era chiaro dalla domanda che questa era la parte che l'OP stava chiedendo. Stavo modificando la mia risposta mentre commentavi.
Bill the Lizard,

Conosco il motivo per cui ho compilato un programma che utilizza la sqrtfunzione e funziona senza includere la libreria tramite -lm. Grazie!
L_K

5

Come detto a breve, la libreria C libc è collegata per impostazione predefinita e questa libreria contiene le implementazioni di stdlib.h, stdio.h e molti altri file di intestazione standard. Solo per aggiungere ad esso, secondo " An Introduction to GCC " il comando linker per un programma "Hello World" di base in C è il seguente:

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

Notare l'opzione -lc nella terza riga che collega la libreria C.


3

Penso che sia un po 'arbitrario. Devi tracciare una linea da qualche parte (quali librerie sono predefinite e quali devono essere specificate).

Ti dà l'opportunità di sostituirlo con uno diverso che ha le stesse funzioni, ma non credo sia molto comune farlo.

EDIT: (dai miei commenti): penso che gcc faccia questo per mantenere la retrocompatibilità con la cc originale. La mia ipotesi sul perché cc fa questo è a causa del tempo di costruzione - cc è stato scritto per macchine con molta meno potenza di quanto abbiamo ora. Molti programmi non hanno matematica in virgola mobile e probabilmente hanno preso tutte le librerie che non erano comunemente usate fuori dai valori predefiniti. Immagino che il tempo di costruzione del sistema operativo UNIX e gli strumenti che ne derivano siano stati la forza trainante.


penso che la mentalità alla base della domanda sia che i contenuti di libm sono in gran parte parte della libreria C standard, perché non sono in libc?
Evan Teran,

1
Il motivo per cui gcc è mantenere la compatibilità con la cc originale in AT&T Unix. Ho usato i 3B2 nel 1988 e hai dovuto -lm per ottenere la matematica. Mi è sembrato del tutto arbitrario in quel momento. In Visual Studio, non ricordo di aver mai dovuto aggiungere la matematica, ma a volte devi aggiungere altre librerie apparentemente c-runtime. Suppongo che i produttori di compilatori abbiano una ragione (tempo di costruzione?), Ma in questo momento, scommetto che gcc sta solo cercando di essere retrocompatibile.
Lou Franco,

3

Se inserisco stdlib.h o stdio.h, non devo collegarli ma devo collegarli quando compilo:

stdlib.h, stdio.hsono i file di intestazione. Li includi per tua comodità. Prevedono quali simboli saranno disponibili solo se si collega nella libreria corretta. Le implementazioni sono nei file della libreria, è lì che vivono le funzioni.

Includere math.hè solo il primo passo per ottenere l'accesso a tutte le funzioni matematiche.

Inoltre, non devi collegarti libmse non usi le sue funzioni, anche se fai un #include <math.h>passo che è solo un passo informativo per te, per il compilatore sui simboli.

stdlib.h, stdio.hfare riferimento alle funzioni disponibili in libc, che risulta essere sempre collegato in modo che l'utente non debba farlo da solo.


2

stdio fa parte della libreria C standard che, per impostazione predefinita, gcc collegherà.

Le implementazioni della funzione matematica si trovano in un file libm separato a cui non è collegato per impostazione predefinita, quindi è necessario specificarlo -lm. A proposito, non esiste alcuna relazione tra quei file di intestazione e file di libreria.


3
lui lo sa ... si sta chiedendo perché
Evan Teran,

Lui dice perché. Simon spiega che alcune librerie sono collegate per impostazione predefinita, come stdio mentre la libreria matematica non è collegata per impostazione predefinita, quindi deve essere specificata.
mnuzzo,

5
Direi che la natura della domanda è chiedersi perché libm non sia collegato di default (o addirittura separato da libc) poiché i suoi contenuti sono in gran parte parte della libreria standard c.
Evan Teran,

2

Mi immagino che sia un modo per rendere le applicazioni che non utilizzano affatto eseguire un po 'meglio. Ecco il mio pensiero su questo.

I sistemi operativi x86 (e immagino che altri) debbano memorizzare lo stato FPU sul cambio di contesto. Tuttavia, la maggior parte dei sistemi operativi si preoccupa solo di salvare / ripristinare questo stato dopo che l'app ha tentato di utilizzare la FPU per la prima volta.

Inoltre, nella libreria matematica è probabilmente presente un codice di base che imposta la FPU su uno stato di base sano quando la libreria viene caricata.

Quindi, se non si collega alcun codice matematico, nulla di tutto ciò accadrà, quindi il sistema operativo non deve salvare / ripristinare alcuno stato FPU, rendendo gli switch di contesto leggermente più efficienti.

Solo un'ipotesi però.

EDIT: in risposta ad alcuni dei commenti, la stessa premessa di base si applica ancora ai casi non FPU (la premessa è che era quello di rendere le app che non utilizzavano libm funzionavano leggermente meglio).

Ad esempio, se esiste una soft-FPU che era molto simile agli inizi di C. Quindi avere libm separato potrebbe impedire a un sacco di codice di grandi dimensioni (e lento se usato) di collegarsi inutilmente.

Inoltre, se è disponibile solo il collegamento statico, si applica un argomento simile che manterrà le dimensioni eseguibili e i tempi di compilazione inferiori.


Se non si collega con libm ma si tocca la FPU x87 con altri mezzi (operazioni su float, per esempio), il kernel x86 deve salvare lo stato FPU. Non credo sia un'ottima ipotesi ...
effimero

ovviamente se si utilizza manualmente la FPU, il kernel dovrà comunque salvare / ripristinare il suo stato. Stavo dicendo che se non lo usi mai (incluso non usare la libm), non dovrai farlo.
Evan Teran,

In realtà può dipendere molto dal kernel. La libreria matematica utilizzata dal kernel potrebbe avere una funzione save_FPU_on_switch () che la attiva, mentre altri rilevano se la FPU è stata toccata.
Earlz,

1
Se ricordo bene, l'intero problema precede a lungo i coprocessori a virgola mobile anche sui microprocessori.
Nosredna,

@earlz: l'approccio di salvare le richieste della libreria matematica sarebbe un progetto terribile. E se usano la FPU in qualche altro modo? L'unico approccio sano (oltre a salvare / ripristinare sempre) sarebbe quello di rilevare l'utilizzo e quindi iniziare a salvare / ripristinare.
Evan Teran,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.