Tại sao bạn phải liên kết thư viện toán học trong C?


254

Nếu tôi bao gồm <stdlib.h>hoặc <stdio.h>trong một chương trình C, tôi không phải liên kết những thứ này khi biên dịch nhưng tôi phải liên kết đến <math.h>, sử dụng -lmvới gcc, ví dụ:

gcc test.c -o test -lm

Lý do cho điều này là gì? Tại sao tôi phải liên kết rõ ràng thư viện toán học mà không phải các thư viện khác?

Câu trả lời:


249

Các hàm trong stdlib.hstdio.hcó các cài đặt trong libc.so(hoặc libc.acho liên kết tĩnh), được liên kết vào tệp thực thi của bạn theo mặc định (như thể -lcđược chỉ định). GCC có thể được hướng dẫn để tránh liên kết tự động này với -nostdlibhoặc -nodefaultlibscác tùy chọn.

Các hàm toán học math.hcó các triển khai trong libm.so(hoặc libm.acho liên kết tĩnh) và libmkhông được liên kết theo mặc định. Có những lý do lịch sử cho việc này libm/ libcchia tách, không ai trong số họ rất thuyết phục.

Thật thú vị, thời gian chạy C ++ libstdc++yêu cầu libm, vì vậy nếu bạn biên dịch chương trình C ++ với GCC ( g++), bạn sẽ tự động được libmliên kết trong.


8
Điều này không liên quan gì đến Linux, vì nó đã phổ biến từ lâu trước Linux. Tôi nghi ngờ nó có liên quan đến việc cố gắng giảm thiểu kích thước thực thi, vì có rất nhiều chương trình không cần các hàm toán học.
David Thornley

39
Trên các hệ thống cổ, nếu các hàm toán học được chứa trong libc, thì việc biên dịch tất cả các chương trình sẽ chậm hơn, các tệp thực thi đầu ra sẽ lớn hơn và thời gian chạy sẽ cần nhiều bộ nhớ hơn, không có lợi cho hầu hết các chương trình không sử dụng các hàm toán học này. Ngày nay, chúng tôi có hỗ trợ tốt cho các thư viện dùng chung và ngay cả khi liên kết tĩnh, các thư viện chuẩn được thiết lập để mã không sử dụng có thể bị loại bỏ, vì vậy không còn lý do nào nữa.
ephemient

38
@ephemient Ngay cả trong những ngày xưa, việc liên kết đến một thư viện không kéo theo tất cả các nội dung của thư viện để thực thi. Trình liên kết, mặc dù một công nghệ thường bị bỏ qua, trong lịch sử đã khá hiệu quả.

7
@ephemient Ngoài ra, các thư viện chia sẻ đã tồn tại lâu hơn bạn nghĩ. Chúng được phát minh vào những năm 1950, không phải những năm 1980.

5
Tôi cho rằng vào cuối ngày những gì chúng ta đang xem không gì khác hơn là sự bảo thủ của GCC: "nó luôn hoạt động như vậy". Tôi chỉ muốn họ áp dụng lý do tương tự cho các phần mở rộng trình biên dịch của họ.

77

Hãy nhớ rằng C là một ngôn ngữ cũ và FPU là một hiện tượng tương đối gần đây. Lần đầu tiên tôi thấy C trên các bộ xử lý 8 bit, trong đó có rất nhiều công việc để thực hiện ngay cả số học số nguyên 32 bit. Nhiều trong số các triển khai này thậm chí không có sẵn một thư viện toán học dấu phẩy động!

Ngay cả trên 68000 máy đầu tiên (Mac, Atari ST, Amiga), bộ đồng xử lý dấu phẩy động thường là các tiện ích bổ sung đắt tiền.

Để làm tất cả toán học dấu phẩy động đó, bạn cần một thư viện khá lớn. Và toán học sẽ chậm. Vì vậy, bạn hiếm khi sử dụng phao. Bạn đã cố gắng làm mọi thứ với số nguyên hoặc số nguyên tỷ lệ. Khi bạn phải bao gồm math.h, bạn nghiến răng. Thông thường, bạn sẽ viết các bảng xấp xỉ và tra cứu của riêng mình để tránh điều đó.

Sự đánh đổi tồn tại trong một thời gian dài. Đôi khi có các gói toán cạnh tranh được gọi là "fastmath" hoặc đại loại như vậy. Giải pháp tốt nhất cho toán học là gì? Thực sự chính xác nhưng công cụ chậm? Không chính xác nhưng nhanh? Bảng lớn cho chức năng trig? Mãi cho đến khi các bộ đồng xử lý được đảm bảo có mặt trong máy tính thì hầu hết các triển khai đều trở nên rõ ràng. Tôi tưởng tượng rằng có một số lập trình viên hiện đang ở đâu đó ngay bây giờ, làm việc trên một con chip nhúng, cố gắng quyết định có nên mang vào thư viện toán học để xử lý một số vấn đề toán học hay không.

Đó là lý do toán học không chuẩn . Nhiều hoặc có thể hầu hết các chương trình đã không sử dụng một float. Nếu các FPU luôn ở xung quanh và những chiếc phao và đôi luôn rẻ để vận hành, thì không còn nghi ngờ gì nữa, đó sẽ là một "stdmath".


Heh, tôi đang sử dụng các xấp xỉ Pade cho (1 + x) ^ y trong Java, trong một máy tính để bàn. Đăng nhập, exp và pow vẫn còn chậm.
quant_dev

Điểm tốt. Và tôi đã thấy các xấp xỉ cho sin () trong các plugin âm thanh.
Nosredna

11
Điều này giải thích tại sao libmkhông được liên kết theo mặc định, nhưng toán học là tiêu chuẩn từ C89 và trước đó, K & R đã thực sự tiêu chuẩn hóa nó, vì vậy nhận xét "stdmath" của bạn không có ý nghĩa.
Fred Foo

@FredFoo Các loại và giao diện đã được chuẩn hóa, nhưng không triển khai. Tôi nghĩ rằng Nosredna đang đề cập đến một thư viện toán học tiêu chuẩn.
Tim Bird

72

Bởi vì thực tế lịch sử vô lý mà không ai sẵn sàng để sửa chữa. Hợp nhất tất cả các chức năng theo yêu cầu của C và POSIX vào một tệp thư viện sẽ không chỉ tránh được câu hỏi này mà còn được tiết kiệm một lượng đáng kể thời gian và bộ nhớ khi liên kết động, vì mỗi .sotệp được liên kết yêu cầu các hoạt động của hệ thống tệp để xác định vị trí và tìm thấy nó, và một vài trang cho các biến tĩnh, định vị lại, v.v.

An thực hiện nơi tất cả các chức năng trong một thư viện và -lm, -lpthread, -lrtvv tùy chọn là tất cả không-ops (hoặc liên kết để trống .afiles) là hoàn toàn tuân thủ QTI POSIX và chắc chắn thích hợp hơn.

Lưu ý: Tôi đang nói về POSIX vì bản thân C không chỉ định bất cứ điều gì về cách trình biên dịch được gọi. Vì vậy, bạn chỉ có thể coi gcc -std=c99 -lmnhư cách cụ thể thực hiện mà trình biên dịch phải được gọi cho hành vi tuân thủ.


9
+1 để chỉ ra rằng POSIX không yêu cầu các thư viện libm, libc và librt riêng biệt tồn tại. Ví dụ, trên Mac OS, mọi thứ đều nằm trong một libSystem duy nhất (bao gồm libdbm, libdl, libgcc_s, libinfo, libm, libpoll, libproc và librpcsvc).
F'x

3
Cẩu1 để suy đoán về việc tra cứu thư viện ảnh hưởng đến hiệu suất mà không cần sao lưu bằng liên kết hoặc số. "Hồ sơ. Đừng suy đoán"
F'x

12
Đây không phải là đầu cơ. Tôi không có bất kỳ bài báo nào được công bố, nhưng tôi đã tự mình thực hiện tất cả các phép đo và sự khác biệt là rất lớn. Chỉ cần sử dụng stracevới một trong các tùy chọn thời gian để xem lượng thời gian khởi động được dành cho liên kết động hoặc so sánh chạy ./configuretrên hệ thống có tất cả các tiện ích tiêu chuẩn được liên kết tĩnh so với liên kết động. Ngay cả các nhà phát triển ứng dụng máy tính để bàn chính và các nhà tích hợp hệ thống cũng nhận thức được chi phí của liên kết động; đây là lý do tại sao những thứ như prelink tồn tại. Tôi chắc rằng bạn có thể tìm thấy điểm chuẩn trong một số bài báo đó.
R .. GitHub DỪNG GIÚP ICE

1
Lưu ý rằng POSIX không yêu cầu -lmphải được chấp nhận và các ứng dụng sử dụng giao diện toán học phải sử dụng -lm, nhưng nó có thể là một tùy chọn nội bộ được xử lý (hoặc thậm chí bỏ qua) bởi lệnh trình biên dịch, không phải là tệp thư viện thực tế. Hoặc nó chỉ có thể là một .atập tin trống nếu các giao diện nằm trong libc chính.
R .. GitHub DỪNG GIÚP ICE

6
@FX: Không biết tại sao tôi quên đề cập đến điều này trước đây: strace -ttsẽ dễ dàng cho bạn thấy thời gian dành cho liên kết động. Nó không đẹp. Và trên Linux, việc kiểm tra /proc/sys/smapssẽ cho bạn thấy chi phí bộ nhớ của các thư viện bổ sung.
R .. GitHub DỪNG GIÚP ICE

33

Bởi vì time()và một số hàm khác được builtinđịnh nghĩa trong chính thư viện C ( libc) và GCC luôn liên kết với libc trừ khi bạn sử dụng -ffreestandingtùy chọn biên dịch. Tuy nhiên, các hàm toán học sống trong libmđó không được liên kết ngầm bởi gcc.


8
Trên LLVM gcc tôi không phải thêm -lm. Tại sao lại thế này?
bot47

26

Một lời giải thích được đưa ra ở đây :

Vì vậy, nếu chương trình của bạn đang sử dụng các hàm toán học và bao gồm math.h , thì bạn cần liên kết rõ ràng thư viện toán học bằng cách chuyển -lmcờ. Lý do cho sự tách biệt đặc biệt này là vì các nhà toán học rất kén chọn cách tính toán của họ và họ có thể muốn sử dụng việc thực hiện các hàm toán học của riêng họ thay vì thực hiện chuẩn. Nếu các hàm toán học được gộp vào libc.athì sẽ không thể làm được điều đó.

[Biên tập]

Tôi không chắc chắn tôi đồng ý với điều này, mặc dù. Nếu bạn có một thư viện cung cấp, giả sử, sqrt()và bạn vượt qua nó trước thư viện chuẩn, một trình liên kết Unix sẽ lấy phiên bản của bạn, phải không?


10
Tôi không nghĩ có một đảm bảo rằng điều đó sẽ xảy ra; thay vào đó bạn có thể kết thúc bằng một xung đột biểu tượng. Nó có thể sẽ phụ thuộc vào trình liên kết và bố cục của thư viện. Tôi vẫn thấy lý do đó là yếu đuối; nếu bạn đang tạo một hàm sqrt tùy chỉnh, bạn thực sự không nên đặt cho nó cùng tên với hàm sqrt tiêu chuẩn, ngay cả khi nó làm điều tương tự ...
ephemient

1
Thật vậy, làm cho hàm của riêng bạn (không tĩnh) có tên sqrtkết quả trong một chương trình với hành vi không xác định.
R .. GitHub DỪNG GIÚP ICE

@Bastien Tìm tốt. Và đến thời điểm của bạn, bạn có ý nghĩa gì bởi "trước thư viện chuẩn"? Tôi nghĩ rằng, thư viện tiêu chuẩn được liên kết theo mặc định và không bắt buộc phải được liên kết thông qua các tùy chọn dòng lệnh. Vì vậy, thư viện tiêu chuẩn sẽ là nơi đầu tiên cho trình liên kết và người ta không thể đặt triển khai của riêng mình "trước thư viện chuẩn".
Rocky Inde

@RockyInde: nhìn vào câu trả lời của tôi, tôi nghĩ rằng tôi thực sự có nghĩa là trước thư viện toán học tiêu chuẩn. Nhưng tôi nghĩ rằng có các tùy chọn trình biên dịch để không liên kết thư viện C tiêu chuẩn, điều này sẽ cho phép bạn vượt qua thư viện của bạn.
Bastien Léonard

@ BastienLéonard Tôi sử dụng gcc của phiên bản 7.2, đây -lmlà tùy chọn hoàn toàn. Mọi ý tưởng
Donghua Liu

5

Có một cuộc thảo luận kỹ lưỡng về việc liên kết với các thư viện bên ngoài trong Giới thiệu về GCC - Liên kết với các thư viện bên ngoài . Nếu một thư viện là thành viên của các thư viện chuẩn (như stdio), thì bạn không cần chỉ định cho trình biên dịch (thực sự là trình liên kết) để liên kết chúng.

EDIT: Sau khi đọc một số câu trả lời và nhận xét khác, tôi nghĩ rằng tài liệu tham khảo libc.a và tài liệu tham khảo libm mà nó liên kết với cả hai có rất nhiều điều để nói về lý do tại sao hai người lại tách biệt.

Lưu ý rằng nhiều hàm trong 'libm.a' (thư viện toán học) được định nghĩa trong 'math.h' nhưng không có trong libc.a. Một số có thể gây nhầm lẫn, nhưng nguyên tắc cơ bản là thế này - thư viện C chứa các hàm mà ANSI ra lệnh phải tồn tại, do đó bạn không cần -lm nếu bạn chỉ sử dụng các hàm ANSI. Ngược lại, 'libm.a' chứa nhiều chức năng hơn và hỗ trợ chức năng bổ sung như gọi lại matherr và tuân thủ một số tiêu chuẩn hành vi thay thế trong trường hợp có lỗi FP. Xem phần libm, để biết thêm chi tiết.


1
Mà không trả lời câu hỏi tại sao bạn phải liên kết trong các thư viện phù hợp riêng. Rõ ràng bạn muốn phải liên kết các thư viện OpenGL một cách riêng biệt, nhưng có thể nói rằng các thư viện toán học thường hữu ích.
David Thornley

@David: Đúng rồi bạn. Tôi không rõ ràng với câu hỏi rằng đây là bit mà OP đang hỏi. Tôi đã chỉnh sửa câu trả lời của tôi khi bạn nhận xét.
Bill Lizard

Tôi biết lý do tôi biên dịch một chương trình sử dụng sqrtchức năng và nó hoạt động mà không bao gồm thư viện thông qua -lm. Cảm ơn!
L_K

5

Như ephemient đã nói, thư viện C libc được liên kết theo mặc định và thư viện này chứa các cài đặt của stdlib.h, stdio.h và một số tệp tiêu đề tiêu chuẩn khác. Chỉ cần thêm vào nó, theo " Giới thiệu về GCC ", lệnh liên kết cho chương trình "Hello World" cơ bản trong C như sau:

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

Lưu ý tùy chọn -lc trong dòng thứ ba liên kết thư viện C.


3

Tôi nghĩ đó là loại tùy ý. Bạn phải vẽ một dòng ở đâu đó (thư viện nào là mặc định và cần được chỉ định).

Nó cho bạn cơ hội để thay thế nó bằng một chức năng khác có cùng chức năng, nhưng tôi không nghĩ việc làm như vậy là rất phổ biến.

EDIT: (từ ý kiến ​​của riêng tôi): Tôi nghĩ gcc làm điều này để duy trì khả năng tương thích ngược với cc ban đầu. Tôi đoán tại sao cc làm điều này là do thời gian xây dựng - cc được viết cho các máy có công suất thấp hơn nhiều so với hiện tại. Rất nhiều chương trình không có toán học dấu phẩy động và có lẽ chúng đã lấy mọi thư viện thường không được sử dụng ngoài mặc định. Tôi đoán rằng thời gian xây dựng HĐH UNIX và các công cụ đi cùng với nó là động lực.


Tôi nghĩ rằng tâm lý đằng sau câu hỏi là nội dung của libm phần lớn là của thư viện C tiêu chuẩn, tại sao họ không ở libc?
Evan Teran

1
Lý do cho gcc là để duy trì khả năng tương thích với cc gốc trong AT & T Unix. Tôi đã sử dụng 3B2 vào năm 1988 và bạn phải -lm để có được toán học. Nó dường như hoàn toàn độc đoán với tôi tại thời điểm đó. Trong Visual Studio, tôi không nhớ đã bao giờ phải thêm toán học, nhưng đôi khi bạn phải thêm các thư viện dường như c-runtime khác. Tôi giả sử rằng các nhà cung cấp trình biên dịch có lý do (thời gian xây dựng?), Nhưng ngay bây giờ, tôi cá rằng gcc chỉ đang cố gắng để tương thích ngược.
Lou Franco

3

Nếu tôi đặt stdlib.h hoặc stdio.h, tôi không phải liên kết những cái đó nhưng tôi phải liên kết khi tôi biên dịch:

stdlib.h, stdio.hlà các tệp tiêu đề. Bạn bao gồm chúng để thuận tiện cho bạn. Họ chỉ dự báo những biểu tượng nào sẽ trở nên khả dụng nếu bạn liên kết trong thư viện thích hợp. Việc triển khai nằm trong các tệp thư viện, đó là nơi các chức năng thực sự sống.

Bao gồm math.hchỉ là bước đầu tiên để có được quyền truy cập vào tất cả các hàm toán học.

Ngoài ra, bạn không phải liên kết với libmnếu bạn không sử dụng các chức năng của nó, ngay cả khi bạn thực hiện một #include <math.h>bước chỉ là thông tin cho bạn, cho trình biên dịch về các ký hiệu.

stdlib.h, stdio.htham khảo các chức năng có sẵn libc, luôn luôn được liên kết để người dùng không phải tự làm.


2

stdio là một phần của thư viện C tiêu chuẩn, theo mặc định, gcc sẽ liên kết với.

Việc triển khai hàm toán học nằm trong một tệp libm riêng biệt không được liên kết theo mặc định, do đó bạn phải chỉ định nó -lm. Nhân tiện, không có mối quan hệ giữa các tệp tiêu đề và tệp thư viện.


3
anh ấy biết điều đó ... anh ấy đang hỏi tại sao
Evan Teran

Anh nói tại sao. Simon giải thích rằng một số thư viện được liên kết theo mặc định, như stdio trong khi thư viện toán học không được liên kết theo mặc định nên phải được chỉ định.
mnuzzo

5
Tôi sẽ nói rằng bản chất của câu hỏi là hỏi tại sao libm không được liên kết theo mặc định (hoặc thậm chí tách biệt với libc) vì nội dung của nó chủ yếu là một phần của thư viện chuẩn c.
Evan Teran

2

Tôi đoán rằng đó là một cách để làm cho các ứng dụng hoàn toàn không sử dụng nó hoạt động tốt hơn một chút. Đây là suy nghĩ của tôi về điều này.

Các hệ điều hành x86 (và tôi tưởng tượng ra các hệ điều hành khác) cần lưu trữ trạng thái FPU trên chuyển đổi ngữ cảnh. Tuy nhiên, hầu hết các hệ điều hành chỉ bận tâm lưu / khôi phục trạng thái này sau khi ứng dụng cố gắng sử dụng FPU lần đầu tiên.

Ngoài ra, có lẽ có một số mã cơ bản trong thư viện toán học sẽ đặt FPU thành trạng thái cơ sở lành mạnh khi thư viện được tải.

Vì vậy, nếu bạn hoàn toàn không liên kết trong bất kỳ mã toán học nào, thì điều này sẽ không xảy ra, do đó HĐH hoàn toàn không phải lưu / khôi phục bất kỳ trạng thái FPU nào, giúp chuyển đổi ngữ cảnh hiệu quả hơn một chút.

Chỉ là một phỏng đoán mặc dù.

EDIT: để đáp ứng với một số ý kiến, tiền đề cơ sở tương tự vẫn được áp dụng cho các trường hợp không phải là FPU (tiền đề là làm cho các ứng dụng không sử dụng libm hoạt động tốt hơn một chút).

Ví dụ: nếu có một FPU mềm giống như trong những ngày đầu của C. Sau đó, việc tách libm có thể ngăn không cho nhiều mã lớn (và chậm nếu được sử dụng) không được liên kết một cách không cần thiết.

Ngoài ra, nếu chỉ có sẵn liên kết tĩnh, thì một đối số tương tự sẽ áp dụng rằng nó sẽ giữ kích thước thực thi và thời gian biên dịch xuống.


Nếu bạn không liên kết với libm nhưng chạm vào x87 FPU thông qua các phương tiện khác (chẳng hạn như thao tác trên phao), hạt nhân x86 không cần phải lưu trạng thái FPU. Tôi không nghĩ rằng đây là một dự đoán rất tốt ...
ephemient

tất nhiên nếu bạn sử dụng thủ công FPU, hạt nhân vẫn sẽ cần lưu / khôi phục trạng thái của nó. Tôi đã nói rằng nếu bạn không bao giờ sử dụng nó (bao gồm cả việc không sử dụng libm) thì nó sẽ không phải.
Evan Teran

Thực sự nó rất có thể phụ thuộc rất nhiều vào kernel. Thư viện toán học mà kernel sử dụng có thể có hàm save_FPU_on_switch () để bật nó, trong khi các thư viện khác chỉ phát hiện nếu chạm vào FPU.
Earlz

1
Nếu tôi nhớ lại một cách chính xác, toàn bộ vấn đề sẽ xảy ra trước các bộ đồng xử lý dấu phẩy động ngay cả trên bộ vi xử lý.
Nosredna

@earlz: phương pháp tiết kiệm yêu cầu thư viện toán học sẽ là một thiết kế tồi tệ. Điều gì xảy ra nếu họ sử dụng FPU bằng một số phương tiện khác? Cách tiếp cận lành mạnh duy nhất (ngoài việc luôn luôn lưu / khôi phục) sẽ là phát hiện việc sử dụng và sau đó bắt đầu lưu / khôi phục.
Evan Teran
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.