GCC 및 ld로 ​​사용하지 않는 C / C ++ 기호를 제거하는 방법은 무엇입니까?


110

내 실행 파일의 크기를 심각하게 최적화 ( ARM개발)해야하는데 현재 빌드 체계 ( gcc+ ld)에서 사용하지 않는 기호가 제거되지 않는다는 것을 알았습니다 .

arm-strip --strip-unneeded결과 실행 파일 / 라이브러리에 대한 의 사용은 실행 파일의 출력 크기를 변경하지 않습니다 (이유를 모르겠습니다 . 단순히 불가능할 수도 있습니다) .

건물 파이프 라인을 수정하여 결과 파일에서 사용되지 않는 기호를 제거 하는 방법 (존재하는 경우) 은 무엇입니까?


난이 생각하지,하지만 내 현재 임베디드 환경은 매우 "강력한"심지어는 저장되지 않습니다 500K밖으로의 2M아주 좋은로드 성능 향상의 결과.

최신 정보:

불행하게도 현재 gcc버전 I의 사용은하지 않는 -dead-strip옵션과 -ffunction-sections... + --gc-sections에 대한 ld결과 출력을위한 의미있는 차이를 제공하지 않습니다.

gcc + ld사용하지 않는 기호를 자동으로 제거해야한다고 확신했기 때문에 이것이 문제가되었다는 사실에 충격을 받았습니다 (왜 유지해야합니까?).


기호가 사용되지 않는다는 것을 어떻게 알 수 있습니까?
zvrba 2011

어디에서나 참조되지 않음 => 최종 응용 프로그램에서 사용되지 않습니다. comipling / linking하는 동안 콜 그래프를 작성하는 것이 그리 어렵지 않을 것이라고 가정합니다.
Yippie-Ki-Yay

1
죽은 기호 를 제거하여 .o 파일의 크기를 줄이려고 하거나 실행 메모리에로드 된 후 실제 코드 공간의 크기를 줄이려고합니까? "임베디드"라고 말하는 사실은 후자를 암시합니다. 당신이 묻는 질문은 전자에 초점을 맞춘 것 같습니다.
Ira Baxter

@Ira 출력 실행 파일 크기를 줄이려고합니다 . 예를 들어boost 라이브러리 를 사용하는 일부 기존 응용 프로그램을 포팅하려고 하면 결과 .exe파일에 사용되지 않은 개체 파일이 많이 포함되어 있고 현재 임베디드 런타임 사양으로 인해 , 10mb응용 프로그램을 시작하는 것은 응용 프로그램을 시작하는 것보다 훨씬 오래 걸립니다 500k.
Yippie-Ki-Yay

8
@Yippie :로드 시간을 최소화하기 위해 코드를 제거하고 싶습니다. 제거하려는 코드는 사용하지 않는 메서드 등입니다. 도서관에서. 예, 이렇게하려면 콜 그래프를 만들어야합니다. 그렇게 쉽지는 않습니다. 글로벌 콜 그래프 여야하고, 보수적이어야하고 (사용될 수있는 것을 제거 할 수 없음) 정확해야합니다 (따라서 이상적인 콜 그래프에 가깝게 유지되므로 그렇지 않은 것이 무엇인지 알 수 있습니다. 익숙한). 큰 문제는 글로벌하고 정확한 콜 그래프를 만드는 것입니다. 링커는 말할 것도없고이 작업을 수행하는 많은 컴파일러를 알지 못합니다.
Ira Baxter

답변:


131

GCC의 경우 다음 두 단계로 수행됩니다.

먼저 데이터를 컴파일하되 번역 단위 내에서 코드를 별도의 섹션으로 분리하도록 컴파일러에 지시하십시오. 이 작업은 다음 두 컴파일러 플래그를 사용하여 함수, 클래스 및 외부 변수에 대해 수행됩니다.

-fdata-sections -ffunction-sections

링커 최적화 플래그를 사용하여 번역 단위를 함께 연결합니다. 이렇게하면 링커가 참조되지 않은 섹션을 삭제합니다.

-Wl,--gc-sections

따라서 두 개의 함수가 선언 된 test.cpp라는 파일이 하나 있는데 그중 하나가 사용되지 않은 경우 gcc (g ++)에 다음 명령을 사용하여 사용하지 않는 파일을 생략 할 수 있습니다.

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

(-Os는 GCC에 크기 최적화를 지시하는 추가 컴파일러 플래그입니다.)


3
이것은 GCC의 옵션 설명에 따라 실행 파일의 속도를 늦출 것입니다 (제가 테스트했습니다).
변태

1
이를 통해 mingwlibstdc ++ 및 libgcc를 플래그로 정적으로 정적으로 링크 할 때 작동하지 않습니다 -static. 링커 옵션 -strip-all은 상당히 도움이되지만 생성 된 실행 파일 (또는 dll)은 Visual Studio가 생성하는 것보다 약 4 배 더 큽니다. 요점은 libstdc++컴파일 방법에 대한 통제권이 없다는 것 입니다. ld유일한 옵션 이 있어야합니다 .
Fabio

34

경우 이 스레드가 믿을 수있다, 당신은 제공해야 -ffunction-sections하고-fdata-sections 자신의 섹션에서 각 기능 및 데이터 객체를 넣어 것입니다 GCC에. 그런 다음 --gc-sections사용하지 않는 섹션을 제거하기 위해 및 GNU ld를 제공 합니다.


6
@MSalters : C 및 C ++ 표준을 위반하기 때문에 기본값이 아닙니다. 갑자기 전역 초기화가 발생하지 않아 일부 프로그래머가 놀라게됩니다.
Ben Voigt 2011

1
@MSalters : 기본 동작을 만들도록 제안한 비표준 동작 차단 옵션을 전달하는 경우에만.
Ben Voigt 2011

1
@MSalters : 프로그램의 올바른 작동에 부작용이 필요한 경우에만 정적 이니셜 라이저를 실행하는 패치를 만들 수 있다면 멋질 것입니다. 불행히도 나는 그것을 완벽하게 수행하려면 종종 중단 문제를 해결해야한다고 생각하므로 때때로 추가 기호를 포함하는 편에서 실수해야 할 것입니다. 기본적으로 Ira가 질문에 대한 그의 의견에서 말하는 것입니다. (BTW "프로그램의 올바른 작동에 필요하지 않습니다"이 용어는 표준에 사용되는 방법을보다 "사용되지 않는"의 다른 정의입니다)
벤 보이트

2
@BenVoigt in C, 전역 초기화는 부작용을 가질 수 없습니다 (초기화는 상수 표현식이어야 함)
MM

2
@Matt :하지만 그것은 C ++에서는 사실이 아닙니다 ... 그리고 그들은 같은 링커를 공유합니다.
Ben Voigt 2014

25

gcc 및 ld 버전에 대한 문서를 확인하고 싶을 것입니다.

그러나 나를 위해 (OS X gcc 4.0.1) ld

-dead_strip

진입 점이나 내 보낸 심볼로 접근 할 수없는 함수와 데이터를 제거합니다.

-dead_strip_dylibs

진입 점 또는 내 보낸 기호로 연결할 수없는 dylib를 제거합니다. 즉, 링크 중에 기호를 제공하지 않은 dylibs에 대한로드 명령 명령 생성을 억제합니다. 이 옵션은 dylib에 중요한 이니셜 라이저가있는 것과 같은 간접적 인 이유로 런타임에 필요한 dylib에 링크 할 때 사용해서는 안됩니다.

그리고이 유용한 옵션

-why_live symbol_name

symbol_name에 대한 참조 체인을 기록합니다. 에만 적용 가능합니다 -dead_strip. 데드 스트립을 제거해야한다고 생각하는 항목이 제거되지 않은 이유를 디버깅하는 데 도움이 될 수 있습니다.

또한 gcc / g ++ man에는 컴파일시 최적화가 활성화 된 경우에만 특정 종류의 데드 코드 제거가 수행된다는 메모가 있습니다.

이러한 옵션 / 조건은 컴파일러에 맞지 않을 수 있지만 문서에서 비슷한 것을 찾는 것이 좋습니다.


이것은 mingw.
Fabio

-dead_stripgcc옵션 이 아닙니다 .
ar2015

20

프로그래밍 습관도 도움이 될 수 있습니다. 예 : static특정 파일 외부에서 액세스하지 않는 기능에 추가 ; 기호에 대해 더 짧은 이름을 사용합니다 (너무 많이는 아니지만 약간 도움이 될 수 있음). const char x[]가능한 경우 사용 하십시오. ... 이 문서 는 동적 공유 객체에 대해 이야기하지만, 따라갈 경우 최종 바이너리 출력 크기를 더 작게 만드는 데 도움이 될 수있는 제안을 포함 할 수 있습니다 (목표가 ELF 인 경우).


4
기호의 짧은 이름을 선택하는 것이 어떻게 도움이됩니까?
fuz

1
기호가 제거되지 않으면 ça va sans dire—하지만 지금 당장 말해야 할 것 같습니다.
ShinTakezou

@fuz이 문서는 동적 공유 객체 (예 : .soLinux)에 대해 이야기 하고 있으므로 Python의 ctypesFFI 모듈 과 같은 API 가 런타임에 이름별로 기호를 조회하는 데 사용할 수 있도록 기호 이름을 유지해야 합니다.
ssokolow

18

대답은 -flto입니다. 컴파일 및 링크 단계 모두에 전달해야합니다. 그렇지 않으면 아무 작업도 수행하지 않습니다.

실제로 매우 잘 작동합니다. 내가 작성한 마이크로 컨트롤러 프로그램의 크기를 이전 크기의 50 % 미만으로 줄였습니다!

불행히도 약간 버그가있는 것 같았습니다. 제대로 빌드되지 않은 경우가있었습니다. 내가 사용하고있는 빌드 시스템 (QBS; 아주 새로운 것) 때문일 수 있지만, 어쨌든 가능하면 최종 빌드에서만 활성화하고 해당 빌드를 철저히 테스트하는 것이 좋습니다.


1
"-Wl,-gc-sections"는 MinGW-W64에서 작동하지 않고 "-flto"는 나에게 적합합니다. 감사합니다
rhbc73

출력 어셈블리는 -flto장면 뒤에서 무엇을하는지 이해가 안 돼 매우 이상 합니다.
ar2015

와 내가 믿는 -flto이 어셈블리에 각 파일을 컴파일되지 않습니다, 그것은 LLVM IR로 컴파일하고, 그들이 하나 개의 컴파일 단위에서 모든 것처럼 다음 마지막 링크를 컴파일합니다. 즉, 사용하지 않는 함수와 인라인 비 함수 static, 그리고 아마도 다른 것들을 제거 할 수 있습니다. llvm.org/docs/LinkTimeOptimization.html
Timmmm을

13

엄격하게 기호에 관한 것은 아니지만 크기를 고려한다면 항상 -Os-s플래그로 컴파일하십시오 . -Os최소 실행 가능 크기에 대한 결과 코드를 최적화하고 실행 파일 -s에서 기호 테이블 및 재배치 정보를 제거합니다.

때로는-작은 크기를 원할 경우-다른 최적화 플래그를 가지고 놀아도 중요 할 수도 있고 그렇지 않을 수도 있습니다. 예를 들어 토글 -ffast-math및 / 또는 -fomit-frame-pointer때때로 수십 바이트까지 절약 할 수 있습니다.


대부분의 최적화 조정은 언어 표준을 준수하는 한 올바른 코드를 생성하지만 -ffast-math완전히 표준을 준수하는 C ++ 코드에서 혼란을 겪었으므로 절대 권장하지 않습니다.
Raptor007 2017

11

Nemo가 제공 한 대답이 올바른 것 같습니다. 이러한 지침이 작동하지 않으면 여기에 설명 된 지침을 사용하여 예제 프로그램을 컴파일 한 연습으로 사용중인 gcc / ld 버전과 관련된 문제 일 수 있습니다.

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

그런 다음 점진적으로 더 공격적인 데드 코드 제거 스위치를 사용하여 코드를 컴파일했습니다.

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

이러한 컴파일 및 링크 매개 변수는 각각 8457, 8164 및 6160 바이트 크기의 실행 파일을 생성했으며, 가장 큰 기여는 'strip-all'선언에서 비롯되었습니다. 플랫폼에서 유사한 감소를 생성 할 수없는 경우 gcc 버전이이 기능을 지원하지 않을 수 있습니다. Linux Mint 2.6.38-8-generic x86_64에서 gcc (4.5.2-8ubuntu4), ld (2.21.0.20110327)를 사용하고 있습니다.


8

strip --strip-unneeded실행 파일의 기호 테이블에서만 작동합니다. 실제로 실행 가능한 코드를 제거하지는 않습니다.

표준 라이브러리는 모든 기능을 별도의 개체 파일로 분할하여 ar. 그런 다음 결과 아카이브를 라이브러리로 연결하면 (즉, -l your_libraryld에 옵션 을 제공 ) ld는 실제로 사용되는 개체 파일과 기호 만 포함합니다.

유사한 사용 질문 에 대한 답변을 찾을 수도 있습니다.


2
라이브러리의 개별 개체 파일은 정적 링크를 수행 할 때만 관련됩니다. 공유 라이브러리를 사용하면 전체 라이브러리가로드되지만 물론 실행 파일에는 포함되지 않습니다.
Jonathan Leffler 2011

4

이것이 최근 기능이기 때문에 이것이 현재의 곤경에 도움이 될지 모르겠지만 전역 방식으로 기호의 가시성을 지정할 수 있습니다. -fvisibility=hidden -fvisibility-inlines-hidden컴파일시 전달 하면 링커가 나중에 불필요한 기호를 제거하는 데 도움이 될 수 있습니다. 실행 파일을 생성하는 경우 (공유 라이브러리가 아닌) 더 이상 할 일이 없습니다.

자세한 정보 (예 : 라이브러리에 대한 세분화 된 접근 방식)는 GCC 위키 에서 사용할 수 있습니다 .


4

GCC 4.2.1 매뉴얼에서 섹션 -fwhole-program:

현재 컴파일 단위가 컴파일중인 전체 프로그램을 나타낸다고 가정합니다. 를 제외 main하고 속성으로 병합 된 모든 공용 함수 및 변수는 externally_visible정적 함수가되고 영향을받는 경우 프로 시저 간 최적화 프로그램에 의해보다 적극적으로 최적화됩니다. 이 옵션은 static단일 파일로 구성된 프로그램에 대해 키워드 를 적절하게 사용하는 것과 동일하지만이 --combine플래그 를 옵션과 함께 사용하면 함수와 변수가 결합 된 전체 컴파일 단위에 대해 로컬이되기 때문에 대부분의 소규모 C 프로그램을 컴파일하는 데 사용할 수 있습니다. 단일 소스 파일 자체.


예, 그러나 아마도 그것은 어떤 종류의 증분 컴파일에서도 작동하지 않으며 아마도 약간 느릴 것입니다.
Timmmm

@Timmmm : 나는 당신이 생각하고 있다고 생각 -flto합니다.
Ben Voigt

예! 나는 나중에 그것을 발견했습니다 (왜 답이 없습니까?). 불행히도 약간 버그가있는 것 같았 기 때문에 최종 빌드에만 권장하고 빌드를 많이 테스트했습니다!
Timmmm

-1

개체 파일 (예 : 실행 파일)에서 이진 제거를 사용하여 모든 기호를 제거 할 수 있습니다.

참고 : 파일 자체를 변경하고 복사본을 만들지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.