.init
/ .fini
không được phản đối. Nó vẫn là một phần của tiêu chuẩn ELF và tôi dám nói nó sẽ là mãi mãi. Mã trong .init
/ .fini
được chạy bởi trình tải / thời gian chạy liên kết khi mã được tải / không tải. Tức là trên mỗi tải ELF (ví dụ mã thư viện dùng chung) .init
sẽ được chạy. Vẫn có thể sử dụng cơ chế đó để đạt được điều tương tự như với __attribute__((constructor))/((destructor))
. Đó là trường học cũ nhưng nó có một số lợi ích.
.ctors
/ .dtors
cơ chế ví dụ yêu cầu hỗ trợ bởi system-rtl / loader / linker-script. Điều này là không chắc chắn có sẵn trên tất cả các hệ thống, ví dụ như các hệ thống nhúng sâu, nơi mã thực thi trên kim loại trần. Tức là ngay cả khi __attribute__((constructor))/((destructor))
được GCC hỗ trợ, không chắc chắn nó sẽ chạy vì nó liên kết với trình liên kết để tổ chức nó và cho trình tải (hoặc trong một số trường hợp, mã khởi động) để chạy nó. Để sử dụng .init
/ .fini
thay vào đó, cách dễ nhất là sử dụng cờ liên kết: -init & -fini (nghĩa là từ dòng lệnh GCC, cú pháp sẽ là -Wl -init my_init -fini my_fini
).
Trên hệ thống hỗ trợ cả hai phương thức, một lợi ích có thể là mã trong .init
được chạy trước .ctors
và mã .fini
sau .dtors
. Nếu thứ tự có liên quan đó là ít nhất một cách thô sơ nhưng dễ dàng để phân biệt giữa các hàm init / exit.
Một nhược điểm lớn là bạn không thể dễ dàng có nhiều hơn một _init
và một _fini
chức năng cho mỗi mô-đun có thể tải và có thể sẽ phải phân đoạn mã nhiều .so
hơn động lực. Một điều nữa là khi sử dụng phương thức liên kết được mô tả ở trên, người ta sẽ thay thế các hàm _init và _fini
mặc định ban đầu (được cung cấp bởi crti.o
). Đây là nơi tất cả các loại khởi tạo thường xảy ra (trên Linux, đây là nơi khởi tạo biến toàn cục được khởi tạo). Một cách xung quanh được mô tả ở đây
Lưu ý trong liên kết ở trên rằng _init()
không cần phải xếp tầng cho bản gốc vì nó vẫn được đặt đúng chỗ. Các call
trong inline lắp ráp tuy nhiên là x86 ghi nhớ và gọi một hàm từ lắp ráp sẽ trông hoàn toàn khác nhau cho nhiều kiến trúc khác (như ARM ví dụ). Mã tức là không minh bạch.
.init
/ .fini
và .ctors
/ .detors
cơ chế là tương tự, nhưng không hoàn toàn. Mã trong .init
/ .fini
chạy "như là". Tức là bạn có thể có một số chức năng trong .init
/ .fini
, nhưng về mặt cú pháp, AFAIK rất khó để đặt chúng ở đó hoàn toàn trong C mà không phá vỡ mã trong nhiều .so
tệp nhỏ .
.ctors
/ .dtors
được tổ chức khác với .init
/ .fini
. .ctors
/ .dtors
phần chỉ là các bảng có con trỏ tới các hàm và "người gọi" là một vòng lặp do hệ thống cung cấp, gọi từng hàm một cách gián tiếp. Tức là người gọi vòng lặp có thể là kiến trúc cụ thể, nhưng vì nó là một phần của hệ thống (nếu nó tồn tại hoàn toàn), điều đó không thành vấn đề.
Đoạn mã sau đây thêm các con trỏ hàm mới vào .ctors
mảng hàm, chủ yếu giống như cách làm __attribute__((constructor))
(phương thức có thể cùng tồn tại với __attribute__((constructor)))
.
#define SECTION( S ) __attribute__ ((section ( S )))
void test(void) {
printf("Hello\n");
}
void (*funcptr)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;
Người ta cũng có thể thêm các con trỏ hàm vào một phần tự phát minh hoàn toàn khác. Một kịch bản liên kết đã sửa đổi và một chức năng bổ sung bắt chước trình tải .ctors
/ .dtors
vòng lặp là cần thiết trong trường hợp đó. Nhưng với nó, người ta có thể đạt được sự kiểm soát tốt hơn đối với thứ tự thực hiện, thêm đối số và trả về mã xử lý eta (Ví dụ, trong một dự án C ++, sẽ hữu ích nếu cần một cái gì đó chạy trước hoặc sau các nhà xây dựng toàn cầu).
Tôi thích __attribute__((constructor))/((destructor))
ở nơi có thể, đó là một giải pháp đơn giản và thanh lịch ngay cả khi nó cảm thấy như gian lận. Đối với các lập trình viên kim loại trần như tôi, điều này không phải lúc nào cũng là một lựa chọn.
Một số tài liệu tham khảo tốt trong cuốn sách Trình liên kết & bộ tải .
#define __attribute__(x)
). Nếu bạn có nhiều thuộc tính, ví dụ,__attribute__((noreturn, weak))
thật khó để "macro out" nếu chỉ có một bộ dấu ngoặc.