Điều gì xảy ra với các biến toàn cục và tĩnh trong thư viện dùng chung khi nó được liên kết động?


127

Tôi đang cố gắng hiểu điều gì xảy ra khi các mô-đun có các biến toàn cục và biến tĩnh được liên kết động với một ứng dụng. Theo các mô-đun, ý tôi là mỗi dự án trong một giải pháp (tôi làm việc rất nhiều với studio hình ảnh!). Các mô-đun này được tích hợp vào * .lib hoặc * đậm hoặc * .exe.

Tôi hiểu rằng nhị phân của một ứng dụng chứa dữ liệu toàn cầu và tĩnh của tất cả các đơn vị dịch thuật riêng lẻ (tệp đối tượng) trong phân đoạn dữ liệu (và chỉ đọc phân đoạn dữ liệu nếu const).

  • Điều gì xảy ra khi ứng dụng này sử dụng mô-đun A với liên kết động thời gian tải? Tôi giả sử DLL có một phần cho toàn cầu và thống kê của nó. Hệ điều hành có tải chúng không? Nếu vậy, họ được tải đến đâu?

  • Và điều gì xảy ra khi ứng dụng sử dụng mô-đun B với liên kết động thời gian chạy?

  • Nếu tôi có hai mô-đun trong ứng dụng của mình, cả hai đều sử dụng A và B, thì các bản sao của toàn cầu A và B được tạo như được đề cập dưới đây (nếu chúng là các quy trình khác nhau)?

  • Các DLL A và B có được quyền truy cập vào các ứng dụng trên toàn cầu không?

(Vui lòng nêu rõ lý do của bạn)

Trích dẫn từ MSDN :

Các biến được khai báo là toàn cục trong tệp mã nguồn DLL được trình biên dịch và trình liên kết coi là các biến toàn cục, nhưng mỗi quá trình tải một DLL đã cho sẽ có bản sao của các biến toàn cục của DLL đó. Phạm vi của các biến tĩnh được giới hạn trong khối trong đó các biến tĩnh được khai báo. Kết quả là, mỗi quá trình có một phiên bản riêng của các biến toàn cục và tĩnh DLL theo mặc định.

và từ đây :

Khi tự động liên kết các mô-đun, có thể không rõ liệu các thư viện khác nhau có phiên bản toàn cầu của riêng họ hay không hoặc liệu toàn cầu có được chia sẻ hay không.

Cảm ơn.


3
Theo mô-đun, bạn có thể có nghĩa là libs . Có một đề xuất để thêm các mô-đun vào tiêu chuẩn C ++ với định nghĩa chính xác hơn về mô-đun sẽ là gì và ngữ nghĩa khác với các thư viện thông thường như bây giờ.
David Rodríguez - dribeas

Ah, nên đã làm rõ điều đó. Tôi coi các dự án khác nhau trong một giải pháp (tôi làm việc rất nhiều với studio trực quan) như các mô-đun. Các mô-đun này được tích hợp vào * .lib hoặc *.
Raja

3
@ DavidRodríguez-dribeas Thuật ngữ "mô-đun" là thuật ngữ kỹ thuật chính xác cho các tệp thực thi độc lập (được liên kết đầy đủ), bao gồm: các chương trình thực thi, thư viện liên kết động (dll) hoặc các đối tượng chia sẻ (.so). Nó là hoàn toàn thích hợp ở đây, và ý nghĩa là chính xác và hiểu rõ. Cho đến khi có một tính năng tiêu chuẩn có tên là "mô-đun", định nghĩa của nó vẫn là tính năng truyền thống, như tôi đã giải thích.
Mikael Persson

Câu trả lời:


176

Đây là một sự khác biệt khá nổi tiếng giữa các hệ thống tương tự Windows và Unix.

Không có vấn đề gì:

  • Mỗi quy trình có không gian địa chỉ riêng, có nghĩa là không bao giờ có bất kỳ bộ nhớ nào được chia sẻ giữa các quy trình (trừ khi bạn sử dụng một số thư viện liên lạc hoặc tiện ích mở rộng liên tiến trình).
  • Các Rule Một Definition (ODR) vẫn được áp dụng, có nghĩa là bạn chỉ có thể có một định nghĩa của biến toàn cầu có thể nhìn thấy liên kết tại thời gian (tĩnh hoặc liên kết động).

Vì vậy, vấn đề quan trọng ở đây là tầm nhìn thực sự .

Trong mọi trường hợp, staticcác biến toàn cục (hoặc hàm) không bao giờ được nhìn thấy từ bên ngoài một mô-đun (dll / so hoặc thực thi). Tiêu chuẩn C ++ yêu cầu những cái này có liên kết bên trong, nghĩa là chúng không thể nhìn thấy bên ngoài đơn vị dịch (trở thành tệp đối tượng) trong đó chúng được xác định. Vì vậy, điều đó giải quyết vấn đề đó.

Nơi nó trở nên phức tạp là khi bạn có externcác biến toàn cục. Ở đây, các hệ thống giống Windows và Unix hoàn toàn khác nhau.

Trong trường hợp Windows (.exe và.), extern biến toàn cục không phải là một phần của các ký hiệu được xuất. Nói cách khác, các mô-đun khác nhau không nhận thức được các biến toàn cục được xác định trong các mô-đun khác. Điều này có nghĩa là bạn sẽ gặp lỗi liên kết nếu bạn thử, ví dụ, để tạo một tệp thực thi được cho là sử dụng một externbiến được định nghĩa trong DLL, vì điều này không được phép. Bạn sẽ cần cung cấp một tệp đối tượng (hoặc thư viện tĩnh) với định nghĩa về biến ngoài đó và liên kết tĩnh với cả tệp thực thi và DLL, dẫn đến hai biến toàn cục riêng biệt (một thuộc về tệp thực thi và một thuộc về DLL ).

Để thực sự xuất một biến toàn cục trong Windows, bạn phải sử dụng một cú pháp tương tự như cú pháp xuất / nhập hàm, nghĩa là:

#ifdef COMPILING_THE_DLL
#define MY_DLL_EXPORT extern "C" __declspec(dllexport)
#else
#define MY_DLL_EXPORT extern "C" __declspec(dllimport)
#endif

MY_DLL_EXPORT int my_global;

Khi bạn làm điều đó, biến toàn cục được thêm vào danh sách các ký hiệu được xuất và có thể được liên kết giống như tất cả các hàm khác.

Trong trường hợp môi trường giống Unix (như Linux), các thư viện động, được gọi là "đối tượng chia sẻ" với phần mở rộng .soxuất tất cả các externbiến toàn cục (hoặc hàm). Trong trường hợp này, nếu bạn thực hiện liên kết thời gian tải từ bất kỳ đâu đến tệp đối tượng được chia sẻ, thì các biến toàn cục sẽ được chia sẻ, tức là được liên kết với nhau như một. Về cơ bản, các hệ thống giống Unix được thiết kế để làm cho nó hầu như không có sự khác biệt giữa liên kết với một thư viện tĩnh hoặc thư viện động. Một lần nữa, ODR áp dụng trên bảng: mộtextern biến toàn cục sẽ được chia sẻ trên các mô-đun, có nghĩa là nó chỉ nên có một định nghĩa trên tất cả các mô-đun được tải.

Cuối cùng, trong cả hai trường hợp, đối với các hệ thống tương tự Windows hoặc Unix, bạn có thể thực hiện liên kết thời gian chạy của thư viện động, tức là sử dụng LoadLibrary()/ GetProcAddress()/ FreeLibrary()hoặc dlopen()/ dlsym()/ dlclose(). Trong trường hợp đó, bạn phải tự lấy một con trỏ tới từng ký hiệu bạn muốn sử dụng và bao gồm các biến toàn cục bạn muốn sử dụng. Đối với các biến toàn cục, bạn có thể sử dụng GetProcAddress()hoặc dlsym()giống như bạn làm cho các hàm, miễn là các biến toàn cục là một phần của danh sách ký hiệu được xuất (theo quy tắc của các đoạn trước).

Và tất nhiên, như một lưu ý cuối cùng cần thiết: nên tránh các biến toàn cục . Và tôi tin rằng văn bản bạn trích dẫn (về những điều "không rõ ràng") đang đề cập chính xác đến sự khác biệt về nền tảng mà tôi vừa giải thích (thư viện động không thực sự được xác định theo tiêu chuẩn C ++, đây là lãnh thổ dành riêng cho nền tảng, có nghĩa là nó là ít đáng tin cậy / di động).


5
Câu trả lời tuyệt vời, cảm ơn bạn! Tôi có một theo dõi: Vì DLL là một đoạn mã và dữ liệu độc lập, nó có phần phân đoạn dữ liệu tương tự như các tệp thực thi không? Tôi đang cố gắng hiểu vị trí và cách dữ liệu này được tải (đến) khi thư viện dùng chung được sử dụng.
Raja

18
@Raja Vâng, DLL có phân đoạn dữ liệu. Trong thực tế, về bản thân các tệp, các tệp thực thi và DLL gần như giống hệt nhau, sự khác biệt thực sự duy nhất là một cờ được đặt trong tệp thực thi để nói rằng nó chứa hàm "chính". Khi một quá trình tải một DLL, phân đoạn dữ liệu của nó được sao chép ở đâu đó vào không gian địa chỉ của quy trình và mã khởi tạo tĩnh (sẽ khởi tạo các biến toàn cục không tầm thường) cũng được chạy trong không gian địa chỉ của quy trình. Việc tải tương tự như đối với tệp thực thi, ngoại trừ không gian địa chỉ quy trình được mở rộng thay vì tạo một địa chỉ mới.
Mikael Persson

4
Làm thế nào về các biến tĩnh được định nghĩa bên trong một hàm nội tuyến của một lớp? ví dụ: xác định "class A {void foo () {static int st_var = 0;}}" trong tệp tiêu đề và đưa nó vào mô-đun A và mô-đun B, A / B sẽ chia sẻ cùng một st_var hay mỗi sẽ có một bản sao riêng?
camino

2
@camino Nếu lớp được xuất (nghĩa là được xác định bằng __attribute__((visibility("default")))), thì A / B sẽ chia sẻ cùng st_var. Nhưng nếu lớp được định nghĩa __attribute__((visibility("hidden"))), thì mô-đun A và mô-đun B sẽ có bản sao riêng, không được chia sẻ.
Wei Guo

1
@camino __declspec (dllexport)
ruipacheco
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.