Làm thế nào để loại bỏ các ký hiệu C / C ++ không sử dụng với GCC và ld?


110

Tôi cần tối ưu hóa kích thước tệp thực thi của mình ( ARMphát triển) và tôi nhận thấy rằng trong lược đồ xây dựng hiện tại ( gcc+ ld) các biểu tượng không sử dụng của tôi không bị loại bỏ.

Việc sử dụng arm-strip --strip-unneededcho các tệp thực thi / thư viện kết quả không thay đổi kích thước đầu ra của tệp thực thi (tôi không biết tại sao, có lẽ nó chỉ đơn giản là không thể) .

Cách (nếu nó tồn tại) sẽ là gì để sửa đổi đường ống xây dựng của tôi, để các ký hiệu không sử dụng bị loại bỏ khỏi tệp kết quả?


Tôi thậm chí sẽ không nghĩ đến điều này, nhưng môi trường nhúng hiện tại của tôi không phải là rất "mạnh mẽ" và tiết kiệm thậm chí 500Kkhông dẫn đến 2Mkết quả tăng hiệu suất tải rất tốt.

Cập nhật:

Thật không may, gccphiên bản hiện tại tôi sử dụng không có -dead-striptùy chọn và -ffunction-sections... + --gc-sectionsfor ldkhông mang lại bất kỳ sự khác biệt đáng kể nào cho kết quả đầu ra.

Tôi bị sốc vì điều này thậm chí đã trở thành một vấn đề, bởi vì tôi chắc chắn rằng điều đó gcc + ldsẽ tự động loại bỏ các ký hiệu không sử dụng (tại sao họ thậm chí phải giữ chúng?).


Làm thế nào để bạn biết rằng các ký hiệu không được sử dụng?
zvrba,

Không được tham chiếu ở bất kỳ đâu => không được sử dụng trong ứng dụng cuối cùng. Tôi giả định rằng việc xây dựng biểu đồ cuộc gọi trong khi kết hợp / liên kết sẽ không khó lắm.
Yippie-Ki-Yay,

1
Bạn đang cố gắng giảm kích thước của tệp .o bằng cách loại bỏ các ký hiệu chết hoặc bạn đang cố giảm kích thước của dấu chân mã thực sau khi được tải vào bộ nhớ thực thi? Thực tế là bạn nói "nhúng" gợi ý ở phần sau; câu hỏi bạn hỏi dường như tập trung vào câu hỏi trước đây.
Ira Baxter

@Ira Tôi đang cố gắng giảm kích thước thực thi đầu ra, bởi vì (ví dụ) nếu tôi cố chuyển một số ứng dụng hiện có sử dụng boostthư viện, .exetệp kết quả chứa nhiều tệp đối tượng không sử dụng và do các thông số kỹ thuật của thời gian chạy nhúng hiện tại của tôi , việc khởi động một 10mbứng dụng mất nhiều thời gian hơn, chẳng hạn như khởi động một 500kứng dụng.
Yippie-Ki-Yay,

8
@Yippie: Bạn muốn loại bỏ mã để giảm thiểu thời gian tải; mã bạn muốn loại bỏ là các phương thức không sử dụng / vv. từ các thư viện. Có, bạn cần xây dựng biểu đồ cuộc gọi để thực hiện việc này. Nó không phải là dễ dàng; nó phải là một biểu đồ cuộc gọi toàn cầu, nó phải thận trọng (không thể loại bỏ thứ gì đó có thể bị sử dụng) và phải chính xác (vì vậy bạn có càng gần biểu đồ cuộc gọi lý tưởng, vì vậy bạn thực sự biết những gì không đã sử dụng). Vấn đề lớn là thực hiện một biểu đồ cuộc gọi toàn cầu, chính xác. Không biết có nhiều trình biên dịch làm được điều này, chứ đừng nói đến trình liên kết.
Ira Baxter

Câu trả lời:


131

Đối với GCC, điều này được thực hiện trong hai giai đoạn:

Đầu tiên hãy biên dịch dữ liệu nhưng yêu cầu trình biên dịch tách mã thành các phần riêng biệt trong đơn vị dịch. Điều này sẽ được thực hiện cho các hàm, lớp và các biến bên ngoài bằng cách sử dụng hai cờ trình biên dịch sau:

-fdata-sections -ffunction-sections

Liên kết các đơn vị dịch với nhau bằng cách sử dụng cờ tối ưu hóa trình liên kết (điều này khiến trình liên kết loại bỏ các phần không được tham chiếu):

-Wl,--gc-sections

Vì vậy, nếu bạn có một tệp được gọi là test.cpp có hai hàm được khai báo trong đó, nhưng một trong số chúng không được sử dụng, bạn có thể bỏ qua tệp không sử dụng bằng lệnh sau thành gcc (g ++):

gcc -Os -fdata-sections -ffunction-sections test.cpp -o test -Wl,--gc-sections

(Lưu ý rằng -Os là một cờ trình biên dịch bổ sung cho biết GCC tối ưu hóa kích thước)


3
Xin lưu ý rằng điều này sẽ làm chậm tệp thực thi theo mô tả tùy chọn của GCC (tôi đã thử nghiệm).
biến thái

1
Với mingw điều này không hoạt động khi liên kết tĩnh tĩnh libstdc ++ và libgcc với cờ -static. Tùy chọn trình liên kết hữu -strip-allích một chút, nhưng tệp thực thi được tạo (hoặc dll) vẫn lớn hơn khoảng 4 lần so với những gì Visual Studio sẽ tạo. Vấn đề là, tôi không kiểm soát libstdc++được cách biên dịch. Nên có một ldlựa chọn duy nhất.
Fabio

34

Nếu chủ đề này là đáng tin cậy, bạn cần cung cấp -ffunction-sections-fdata-sections cho gcc, sẽ đặt từng chức năng và đối tượng dữ liệu trong phần riêng của nó. Sau đó, bạn cung cấp và --gc-sectionscho GNU ld để loại bỏ các phần không sử dụng.


6
@MSalters: Nó không phải là mặc định, vì nó vi phạm các tiêu chuẩn C và C ++. Việc khởi tạo toàn cục đột ngột không xảy ra, điều này dẫn đến một số lập trình viên rất ngạc nhiên.
Ben Voigt

1
@MSalters: Chỉ khi bạn vượt qua các tùy chọn phá vỡ hành vi không chuẩn mà bạn đã đề xuất để đặt hành vi mặc định.
Ben Voigt

1
@MSalters: Nếu bạn có thể tạo một bản vá chạy các trình khởi tạo tĩnh nếu và chỉ khi các tác dụng phụ cần thiết đối với hoạt động chính xác của chương trình, thì điều đó thật tuyệt vời. Thật không may, tôi nghĩ rằng làm điều đó một cách hoàn hảo thường đòi hỏi phải giải quyết vấn đề tạm dừng, vì vậy có thể bạn sẽ cần phải bổ sung thêm một số ký hiệu đôi khi. Về cơ bản, đó là những gì Ira nói trong bình luận của mình cho câu hỏi. (BTW: "Không cần thiết cho hoạt động chính xác của chương trình" là một định nghĩa khác nhau của "không sử dụng" hơn như thế nào thuật ngữ được sử dụng trong tiêu chuẩn)
Ben Voigt

2
@BenVoigt trong C, khởi tạo toàn cục không thể có tác dụng phụ (các trình khởi tạo phải là biểu thức không đổi)
MM

2
@Matt: Nhưng điều đó không đúng trong C ++ ... và chúng chia sẻ cùng một trình liên kết.
Ben Voigt

25

Bạn sẽ muốn kiểm tra tài liệu của mình cho phiên bản gcc & ld:

Tuy nhiên đối với tôi (OS X gcc 4.0.1) Tôi tìm thấy những thứ này cho ld

-dead_strip

Loại bỏ các chức năng và dữ liệu không thể truy cập bằng điểm nhập hoặc các ký hiệu đã xuất.

-dead_strip_dylibs

Loại bỏ các tiêu đề không thể truy cập được bằng điểm nhập hoặc các ký hiệu đã xuất. Đó là, ngăn chặn việc tạo ra các lệnh lệnh tải cho các dylibs không cung cấp ký hiệu nào trong liên kết. Tùy chọn này không nên được sử dụng khi liên kết với dylib được yêu cầu trong thời gian chạy vì một số lý do gián tiếp chẳng hạn như dylib có bộ khởi tạo quan trọng.

Và tùy chọn hữu ích này

-why_live symbol_name

Ghi nhật ký một chuỗi tham chiếu đến tên_biểu tượng. Chỉ áp dụng với -dead_strip. Nó có thể giúp gỡ lỗi tại sao một thứ gì đó mà bạn cho rằng nên loại bỏ dải chết lại không được loại bỏ.

Cũng có một lưu ý trong gcc / g ++ man rằng loại bỏ mã chết nhất định chỉ được thực hiện nếu tối ưu hóa được bật khi biên dịch.

Mặc dù các tùy chọn / điều kiện này có thể không phù hợp với trình biên dịch của bạn, nhưng tôi khuyên bạn nên tìm thứ gì đó tương tự trong tài liệu của mình.


Điều này dường như không làm gì với mingw.
Fabio

-dead_stripkhông phải là một gcclựa chọn.
ar2015 23/09/18

20

Thói quen lập trình cũng có thể hữu ích; ví dụ như thêm staticvào các chức năng không được truy cập bên ngoài một tệp cụ thể; sử dụng tên ngắn hơn cho các biểu tượng (có thể giúp ích một chút, có thể không quá nhiều); sử dụng const char x[]nếu có thể; ... bài báo này , mặc dù nó nói về các đối tượng được chia sẻ động, có thể chứa các đề xuất, nếu được làm theo, có thể giúp làm cho kích thước đầu ra nhị phân cuối cùng của bạn nhỏ hơn (nếu mục tiêu của bạn là ELF).


4
Việc chọn tên ngắn hơn cho các ký hiệu giúp ích như thế nào?
fuz

1
nếu các biểu tượng không bị loại bỏ, ça va sans aw — nhưng có vẻ như nó cần được nói ngay bây giờ.
ShinTakezou

@fuz Bài báo đang nói về các đối tượng được chia sẻ động (ví dụ: .sotrên Linux), vì vậy các tên ký hiệu phải được giữ lại để các API như ctypesmô-đun FFI của Python có thể sử dụng chúng để tra cứu các ký hiệu theo tên trong thời gian chạy.
ssokolow

18

Câu trả lời là -flto. Bạn phải chuyển nó cho cả hai bước biên dịch và liên kết của mình, nếu không nó sẽ không làm được gì cả.

Nó thực sự hoạt động rất tốt - đã giảm kích thước của một chương trình vi điều khiển mà tôi đã viết xuống dưới 50% kích thước trước đó của nó!

Thật không may, nó có vẻ hơi lỗi - tôi đã có những trường hợp mọi thứ không được xây dựng chính xác. Có thể là do hệ thống xây dựng tôi đang sử dụng (QBS; nó rất mới), nhưng trong mọi trường hợp, tôi khuyên bạn chỉ nên bật nó cho bản dựng cuối cùng của mình nếu có thể và kiểm tra bản dựng đó một cách kỹ lưỡng.


1
"-Wl, - gc-section" không hoạt động trên MinGW-W64, "-flto" hoạt động với tôi. Cảm ơn
rhbc73

Việc lắp ráp đầu ra rất kỳ lạ và -fltotôi không hiểu nó làm gì ở hậu trường.
ar2015 23/09/18

Tôi tin rằng -fltonó không biên dịch từng tệp thành tập hợp, nó biên dịch chúng thành LLVM IR, và sau đó liên kết cuối cùng sẽ biên dịch chúng như thể tất cả chúng nằm trong một đơn vị biên dịch. Điều đó có nghĩa là nó có thể loại bỏ các chức năng không sử dụng và các chức năng không phải là nội tuyến static, và có thể cả những thứ khác. Xem llvm.org/docs/LinkTimeOptimization.html
Timmmm 23/09/18

13

Mặc dù không nghiêm túc về các ký hiệu, nhưng nếu nói về kích thước - hãy luôn biên dịch với -Os-scờ. -Ostối ưu hóa mã kết quả cho kích thước thực thi tối thiểu và -sxóa bảng ký hiệu và thông tin di dời khỏi tệp thực thi.

Đôi khi - nếu kích thước nhỏ là mong muốn - chơi xung quanh với các cờ tối ưu hóa khác nhau có thể - hoặc có thể không - có ý nghĩa. Ví dụ: chuyển đổi -ffast-mathvà / hoặc đôi khi -fomit-frame-pointercó thể tiết kiệm cho bạn thậm chí hàng chục byte.


Hầu hết các chỉnh sửa tối ưu hóa sẽ vẫn mang lại mã chính xác miễn là bạn tuân thủ tiêu chuẩn ngôn ngữ, nhưng tôi đã gặp -ffast-mathrắc rối trong mã C ++ hoàn toàn tuân thủ tiêu chuẩn, vì vậy tôi sẽ không bao giờ đề xuất nó.
Raptor007

11

Với tôi, có vẻ như câu trả lời do Nemo cung cấp là chính xác. Nếu những hướng dẫn đó không hoạt động, sự cố có thể liên quan đến phiên bản gcc / ld mà bạn đang sử dụng, vì bài tập tôi đã biên soạn một chương trình ví dụ sử dụng hướng dẫn chi tiết tại đây

#include <stdio.h>
void deadcode() { printf("This is d dead codez\n"); }
int main(void) { printf("This is main\n"); return 0 ; }

Sau đó, tôi đã biên dịch mã bằng cách sử dụng các công tắc xóa mã chết tích cực hơn dần dần:

gcc -Os test.c -o test.elf
gcc -Os -fdata-sections -ffunction-sections test.c -o test.elf -Wl,--gc-sections
gcc -Os -fdata-sections -ffunction-sections test.c -o test.elf -Wl,--gc-sections -Wl,--strip-all

Các tham số biên dịch và liên kết này tạo ra các tệp thực thi có kích thước 8457, 8164 và 6160 byte tương ứng, đóng góp đáng kể nhất đến từ khai báo 'dải-tất cả'. Nếu bạn không thể tạo ra các mức giảm tương tự trên nền tảng của mình, thì có thể phiên bản gcc của bạn không hỗ trợ chức năng này. Tôi đang sử dụng gcc (4.5.2-8ubuntu4), ld (2.21.0.20110327) trên Linux Mint 2.6.38-8-generic x86_64


8

strip --strip-unneededchỉ hoạt động trên bảng biểu tượng của tệp thực thi của bạn. Nó không thực sự loại bỏ bất kỳ mã thực thi nào.

Các thư viện tiêu chuẩn đạt được kết quả như bạn mong đợi bằng cách tách tất cả các chức năng của chúng thành các tệp đối tượng riêng biệt, được kết hợp bằng cách sử dụng ar. Sau đó, nếu bạn liên kết kho lưu trữ kết quả dưới dạng thư viện (tức là cung cấp tùy chọn -l your_librarycho ld) thì ld sẽ chỉ bao gồm các tệp đối tượng và do đó các ký hiệu, được thực sự sử dụng.

Bạn cũng có thể tìm thấy một số câu trả lời cho câu hỏi sử dụng tương tự này .


2
Các tệp đối tượng riêng biệt trong thư viện chỉ có liên quan khi thực hiện liên kết tĩnh. Với các thư viện dùng chung, toàn bộ thư viện được tải, nhưng tất nhiên không được đưa vào tệp thực thi.
Jonathan Leffler

4

Tôi không biết liệu điều này có giúp ích cho tình trạng khó khăn hiện tại của bạn hay không vì đây là một tính năng gần đây, nhưng bạn có thể chỉ định khả năng hiển thị của các biểu tượng theo cách toàn cầu. Việc vượt qua -fvisibility=hidden -fvisibility-inlines-hiddenlúc biên dịch có thể giúp trình liên kết sau này loại bỏ các ký hiệu không cần thiết. Nếu bạn đang tạo tệp thực thi (trái ngược với thư viện được chia sẻ) thì không cần làm gì nữa.

Thông tin thêm (và một cách tiếp cận chi tiết cho ví dụ như thư viện) có sẵn trên GCC wiki .


4

Từ hướng dẫn GCC 4.2.1, phần -fwhole-program:

Giả sử rằng đơn vị biên dịch hiện tại đại diện cho toàn bộ chương trình đang được biên dịch. Tất cả các hàm và biến công khai ngoại trừ mainvà các biến được hợp nhất bởi thuộc tính externally_visibletrở thành các hàm tĩnh và trong một ảnh hưởng được tối ưu hóa mạnh mẽ hơn bởi các trình tối ưu hóa liên thủ tục. Mặc dù tùy chọn này tương đương với việc sử dụng statictừ khóa thích hợp cho các chương trình bao gồm một tệp duy nhất, kết hợp với tùy chọn --combinecờ này có thể được sử dụng để biên dịch hầu hết các chương trình C quy mô nhỏ hơn vì các hàm và biến trở thành cục bộ cho toàn bộ đơn vị biên dịch kết hợp, không phải cho chính tệp nguồn duy nhất.


Vâng, nhưng điều đó có lẽ không hoạt động với bất kỳ loại biên dịch gia tăng nào và có lẽ sẽ hơi chậm.
Timmmm

@Timmmm: Tôi nghi ngờ bạn đang nghĩ đến -flto.
Ben Voigt

Đúng! Sau đó tôi thấy rằng (tại sao nó không phải là bất kỳ câu trả lời nào?). Thật không may, nó có vẻ hơi lỗi, vì vậy tôi chỉ giới thiệu nó cho bản dựng cuối cùng và sau đó thử nghiệm bản dựng đó thật nhiều!
Timmmm

-1

Bạn có thể sử dụng nhị phân dải trên tệp đối tượng (ví dụ: tệp thực thi) để tách tất cả các ký hiệu khỏi tệp đó.

Lưu ý: nó tự thay đổi tệp và không tạo bản sao.

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.