2015-04-10 17 views
65

Hôm nay, khi làm việc với một thư viện tùy chỉnh, tôi đã tìm thấy một hành vi lạ. Mã thư viện tĩnh chứa hàm gỡ lỗi main(). Nó không nằm trong cờ #define. Vì vậy, nó có mặt trong thư viện. Và nó được sử dụng liên kết đến một chương trình khác có chứa main() thực.
Khi cả hai liên kết được liên kết với nhau, trình liên kết không đưa ra một lỗi khai báo nhiều cho main(). Tôi đã tự hỏi làm thế nào điều này có thể xảy ra.Chương trình C này biên dịch và chạy với hai chức năng chính như thế nào?

Để làm cho nó đơn giản, tôi đã tạo ra một chương trình mẫu mà mô phỏng hành vi tương tự:

$ cat prog.c 
#include <stdio.h> 
int main() 
{ 
     printf("Main in prog.c\n"); 
} 

$ cat static.c 
#include <stdio.h> 
int main() 
{ 
     printf("Main in static.c\n"); 
} 

$ gcc -c static.c 
$ ar rcs libstatic.a static.o 
$ gcc prog.c -L. -lstatic -o 2main 
$ gcc -L. -lstatic -o 1main 

$ ./2main 
Main in prog.c 
$ ./1main 
Main in static.c 

Làm thế nào để "2main" nhị phân tìm mà main để thực hiện?

Nhưng biên soạn cả trong số họ cùng nhau đưa ra một lỗi khai nhiều:

$ gcc prog.c static.o 
static.o: In function `main': 
static.c:(.text+0x0): multiple definition of `main' 
/tmp/ccrFqgkh.o:prog.c:(.text+0x0): first defined here 
collect2: ld returned 1 exit status 

bất cứ ai có thể vui lòng giải thích hành vi này?

+1

Nếu bạn chạy 'nm' trên thư viện tĩnh có chứa' main' đầu tiên, nó có biểu thị rằng 'main' được định nghĩa (hiện tại và không có' U' bên cạnh nó) không? – anol

+0

liên kết động sẽ chỉ xem xét các biểu tượng cần thiết và sẽ thả chính trong libstatic.a – tristan

+2

Một số khác có câu trả lời, nhưng tôi muốn nhận xét rằng mã bạn đang biên dịch vi phạm "Quy tắc một định nghĩa" (ODR), có nghĩa là hành vi không xác định. Bạn chỉ cần nhìn thấy những gì mà hành vi không xác định trông giống như cho một trình biên dịch. –

Trả lời

57

Trích dẫn ld (1):

The linker will search an archive only once, at the location where it is specified on the command line. If the archive defines a symbol which was undefined in some object which appeared before the archive on the command line, the linker will include the appropriate file(s) from the archive.

Khi liên kết 2main, biểu tượng chính được giải quyết trước khi ld đạt -lstatic, vì ld nhặt nó lên từ prog.o.

Khi liên kết 1main, bạn không xác định chính vào thời điểm nó được đặt ở chế độ -lstatic, do đó, tìm kiếm lưu trữ cho chính.

Logic này chỉ áp dụng cho lưu trữ (thư viện tĩnh), không phải đối tượng thông thường. Khi bạn liên kết prog.o và static.o, tất cả các ký hiệu từ cả hai đối tượng đều được bao gồm vô điều kiện, do đó bạn nhận được lỗi định nghĩa trùng lặp.

+14

Lưu ý rằng hành vi này là trình biên dịch cụ thể (hoặc, tốt, liên kết cụ thể): A Microsoft linker sẽ tìm kiếm tất cả các tập tin đối tượng và thư viện trên dòng lệnh của nó, và làm rối một lỗi nếu một biểu tượng là trùng lặp. – Medinoc

17

Khi bạn liên kết thư viện tĩnh (.a), trình liên kết chỉ tìm kiếm lưu trữ nếu có bất kỳ ký hiệu không xác định nào được theo dõi cho đến thời điểm này. Nếu không, nó không nhìn vào kho lưu trữ cả. Vì vậy, trường hợp 2main của bạn, nó không bao giờ xem lưu trữ vì nó không có bất kỳ biểu tượng không xác định nào để tạo đơn vị dịch.

Nếu bạn bao gồm một chức năng đơn giản trong static.c:

#include <stdio.h> 
void fun() 
{ 
     printf("This is fun\n"); 
} 
int main() 
{ 
     printf("Main in static.c\n"); 
} 

và gọi nó là từ prog.c, sau đó mối liên kết sẽ bị buộc phải nhìn vào các kho lưu trữ để tìm ra biểu tượng fun và bạn sẽ nhận được cùng nhiều chính lỗi định nghĩa là liên kết sẽ tìm thấy biểu tượng trùng lặp main ngay bây giờ.

Khi bạn biên dịch trực tiếp các tệp đối tượng (như trong gcc a.o b.o), trình liên kết không có bất kỳ vai trò nào ở đây và tất cả các biểu tượng được bao gồm để tạo một nhị phân đơn.

Điểm mấu chốt là trình liên kết chỉ xem lưu trữ nếu có biểu tượng bị thiếu. Nếu không, nó là tốt như không liên kết với bất kỳ thư viện.

+0

Tôi đã sửa đổi static.c như bạn đã nói và gọi nó từ prog.c. Bây giờ các mối liên kết hạnh phúc cho thấy nhiều lỗi định nghĩa. Vì vậy, tôi tự hỏi liệu điều này có đúng với các thư viện được chia sẻ không. Nhưng có vẻ như không. '$ gcc -c -fPIC static.c $ gcc -shared -o libsharedlib.so static.o $ gcc -L. -lsharedlib prog.c -o 2mainso $ ./2mainso Đây là niềm vui Chính trong prog.c ' Bạn cũng có thể giải thích điều này không? – MrPavanayi

+1

Tôi không có chuyên gia về libs được chia sẻ, nhưng afaik, tra cứu biểu tượng thư viện được chia sẻ tương tự như tĩnh ngoại trừ "điểm vào" cho biểu tượng được yêu cầu trong thư viện được xác định và lưu trữ bởi trình liên kết. Vì vậy, có vẻ như khi bạn có .so chứa ('fun' và một bản sao' main'), linker sẽ chỉ lưu trữ "entry point" cho 'fun'. Đó là cách giải thích của tôi dựa trên những gì 'nm' cho thấy (chỉ' U' là có 'fun' khác hơn là các cuộc gọi libc). –

1

Sau khi trình liên kết tải bất kỳ tệp đối tượng nào, nó tìm kiếm thư viện cho các ký hiệu không xác định. Nếu không có thì không cần đọc thư viện. Kể từ khi chính đã được xác định, ngay cả khi nó tìm thấy một chính trong mỗi thư viện, không có lý do để tải một thứ hai.Tuy nhiên,

Các trình liên kết có hành vi khác nhau đáng kể. Ví dụ: nếu thư viện của bạn bao gồm tệp đối tượng với cả hai hàm main() và foo() trong đó và foo không được xác định, bạn rất có thể sẽ gặp lỗi cho biểu tượng được định nghĩa nhân chính main().

Trình liên kết hiện đại (tautology) sẽ bỏ qua các ký hiệu toàn cầu từ các đối tượng không thể truy cập được - ví dụ: AIX. Các trình liên kết kiểu cũ giống như những trình tìm thấy trên Solaris và các hệ thống Linux vẫn hoạt động giống như các liên kết unix từ những năm 1970, tải tất cả các ký hiệu từ một mô-đun đối tượng, có thể truy cập được hay không. Điều này có thể là một nguồn của bloat khủng khiếp cũng như thời gian liên kết quá nhiều.

Đặc điểm của liên kết * nix là chúng chỉ tìm kiếm thư viện một cách hiệu quả một lần cho mỗi lần được liệt kê. Điều này đặt một yêu cầu trên lập trình viên để đặt hàng các thư viện trên dòng lệnh tới một trình liên kết hoặc trong một tệp tạo, ngoài việc viết một chương trình. Không yêu cầu danh sách thư viện có thứ tự không phải là hiện đại. Các hệ điều hành cũ hơn thường có các trình liên kết có thể tìm kiếm tất cả các thư viện liên tục cho đến khi vượt qua một thất bại để giải quyết một biểu tượng.

Các vấn đề liên quan