코드에서 어떤 부분이 사용되지 않는지 어떻게 알 수 있습니까?
사용하지 않는 코드를 제거 해야하는 레거시 C ++ 코드가 있습니다. 문제는 코드 기반이 크다는 것입니다.
어떤 코드가 절대 호출 / 사용되지 않는지 어떻게 알 수 있습니까?
사용되지 않는 코드에는 두 가지 종류가 있습니다.
- 로컬 함수, 즉 일부 함수에서는 일부 경로 또는 변수가 사용되지 않습니다 (또는 쓰지만 읽지 않는 것과 같이 의미있는 방식으로 사용되지 않음)
- 글로벌 함수 : 절대 호출되지 않는 함수, 절대 액세스 할 수없는 글로벌 오브젝트
첫 번째 종류의 경우 좋은 컴파일러가 도움이 될 수 있습니다.
-Wunused
(GCC, Clang )는 미사용 변수에 대해 경고해야하며, Clang 미사용 분석기는 읽지 않은 변수에 대해 경고하기 위해 증가되었습니다 (사용 되더라도).-Wunreachable-code
(이전 GCC, 2010 에서 제거됨 )는 액세스하지 않은 로컬 블록에 대해 경고해야합니다 (항상 true로 평가되는 초기 반환 또는 조건에서 발생)catch
컴파일러가 일반적으로 예외가 발생하지 않는다는 것을 증명할 수 없기 때문에 사용하지 않는 블록 에 대해 경고하는 옵션은 없습니다 .
두 번째 종류의 경우 훨씬 더 어렵습니다. 정적으로 전체 프로그램 분석이 필요하며 링크 시간 최적화가 실제로 데드 코드를 제거 할 수는 있지만 실제로 프로그램은 수행 될 때 너무 많이 변환되어 의미있는 정보를 사용자에게 전달하는 것이 거의 불가능합니다.
따라서 두 가지 접근 방식이 있습니다.
- 이론적 인 것은 정적 분석기를 사용하는 것입니다. 전체 코드를 한 번에 자세히 검사하고 모든 흐름 경로를 찾는 소프트웨어입니다. 실제로 나는 여기서 작동하는 것을 모른다.
- 실용적인 방법은 휴리스틱을 사용하는 것입니다. 코드 커버리지 도구를 사용하십시오 (GNU 체인에서는
gcov
. 컴파일 중에는 특정 플래그를 전달해야 제대로 작동합니다). 다양한 범위의 입력 (단위 테스트 또는 회귀 테스트가 아님)으로 코드 적용 범위 도구를 실행하면 데드 코드는 반드시 도달 할 수없는 코드 내에 있으므로 여기에서 시작할 수 있습니다.
주제에 관심이 있고 실제로 도구를 직접 연습 할 시간과 성향이 있다면 Clang 라이브러리를 사용하여 그러한 도구를 만드는 것이 좋습니다.
- Clang 라이브러리를 사용하여 AST (추상 구문 트리)를 얻습니다.
- 진입 점부터 마크 앤 스윕 분석 수행
Clang은 코드를 구문 분석하고 과부하 해결을 수행하므로 C ++ 언어 규칙을 처리 할 필요가 없으며 당면한 문제에 집중할 수 있습니다.
그러나 이러한 종류의 기술은 사용하지 않는 가상 재정의를 식별 할 수 없습니다. 이유는 신뢰할 수없는 타사 코드에 의해 호출 될 수 있기 때문입니다.
사용하지 않는 전체 함수 (및 사용되지 않은 전역 변수)의 경우 GCC와 GNU ld를 사용하는 경우 GCC는 실제로 대부분의 작업을 수행 할 수 있습니다.
소스를 컴파일 할 때는 -ffunction-sections
및을 사용 하고 -fdata-sections
링크 할 때는을 사용하십시오 -Wl,--gc-sections,--print-gc-sections
. 링커는 이제 호출되지 않았기 때문에 제거 할 수있는 모든 함수와 참조되지 않은 모든 전역을 나열합니다.
(물론, --print-gc-sections
파트를 건너 뛰고 링커에서 함수를 자동으로 제거하지만 소스에 유지하도록 할 수도 있습니다.)
참고 : 이것은 사용되지 않은 완전한 함수 만 찾을 수 있으며 함수 내의 데드 코드에 대해서는 아무 것도 수행하지 않습니다. 라이브 함수의 데드 코드에서 호출 된 함수도 유지됩니다.
일부 C ++ 관련 기능은 특히 다음과 같은 문제를 일으킬 수 있습니다.
- 가상 기능. 어떤 서브 클래스가 존재하고 어떤 서브 클래스가 런타임에 실제로 인스턴스화되는지 알지 못하면 최종 프로그램에 어떤 가상 함수가 있어야하는지 알 수 없습니다. 링커에는 그것에 대한 충분한 정보가 없으므로 모든 정보를 유지해야합니다.
- 생성자와 생성자가있는 전역. 일반적으로 링커는 전역의 생성자가 부작용이 없음을 알 수 없으므로 실행해야합니다. 분명히 이것은 지구 자체도 유지되어야 함을 의미합니다.
두 경우 모두, 아무것도 사용하는 가상 함수 나 전역 변수 생성자는 주위에 유지되어야한다.
공유 라이브러리를 구축하는 경우 GCC의 기본 설정 은 공유 라이브러리의 모든 기능 을 내보내 링커에 관한 한 "사용"될 수 있다는 추가 경고가 있습니다. 이 문제를 해결하려면 내보내기 대신 기호를 숨기도록 기본값을 설정 -fvisibility=hidden
한 다음 (예 :) 내 보내야하는 내 보낸 함수를 명시 적으로 선택해야합니다.
g ++을 사용하면이 플래그를 사용할 수 있습니다 -Wunused
문서에 따르면 :
변수가 선언과 별도로 사용되지 않고, 함수가 정적으로 선언되었지만 정의되지 않은 경우, 레이블이 선언되었지만 사용되지 않은 경우 및 명령문이 명시 적으로 사용되지 않은 결과를 계산할 때마다 경고합니다.
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
편집 : 다른 유용한 플래그는 다음과 같습니다 -Wunreachable-code
.
이 옵션은 일부 조건이 충족되지 않거나 절대 반환되지 않는 프로 시저 이후이므로 컴파일러가 소스 코드의 전체 라인이 실행되지 않을 것임을 감지 할 때 경고하기위한 것입니다.
업데이트 : 레거시 C / C ++ 프로젝트 에서 비슷한 주제 데드 코드 감지를 발견 했습니다.
코드 커버리지 도구를 찾고 있다고 생각합니다 . 코드 커버리지 도구는 코드가 실행되는 동안 코드를 분석하고, 실행 된 코드 줄과 몇 번, 그리고 어떤 코드 줄이 아닌지 알려줍니다.
이 오픈 소스 코드 커버리지 툴에 TestCocoon -C / C ++ 및 C # 용 코드 커버리지 툴을 제공 할 수 있습니다.
실제 답변은 다음과 같습니다. 절대 확실하게 알 수 없습니다.
최소한 사소한 경우에는 모든 것을 얻었을 수도 있습니다. 도달 할 수없는 코드에 대한 Wikipedia의 기사에서 다음을 고려하십시오 .
double x = sqrt(2);
if (x > 5)
{
doStuff();
}
Wikipedia가 올바르게 지적했듯이 영리한 컴파일러는 이와 같은 것을 잡을 수 있습니다. 그러나 수정을 고려하십시오.
int y;
cin >> y;
double x = sqrt((double)y);
if (x != 0 && x < 1)
{
doStuff();
}
컴파일러가 이것을 잡을까요? 아마도. 그러나이를 위해서는 sqrt
상수 스칼라 값에 대해 실행 하는 것 이상을 수행해야 합니다. 그것은 그 파악해야한다 (double)y
항상 정수 (쉬운) 될 것이며, 다음의 수학 범위 이해 sqrt
정수 (하드)의 세트를. 매우 복잡한 컴파일러는 sqrt
함수 또는 math.h의 모든 함수 또는 도메인을 파악할 수있는 고정 입력 함수에 대해이 작업을 수행 할 수 있습니다. 이것은 매우 복잡해지며 복잡성은 기본적으로 무한합니다. 컴파일러에 정교함을 계속 추가 할 수 있지만 주어진 입력 세트에 도달 할 수없는 일부 코드를 몰래 숨길 수있는 방법이 항상 있습니다.
그리고 입력 되지 않은 입력 세트 가 있습니다. 실생활에서 의미가 없거나 다른 곳의 검증 로직에 의해 차단되는 입력. 컴파일러가 이에 대해 알 수있는 방법이 없습니다.
이것의 최종 결과는 다른 사람들이 언급 한 소프트웨어 도구가 매우 유용하지만 나중에 수동으로 코드를 거치지 않으면 모든 것을 포착했는지 확실하게 알 수 없다는 것입니다. 그럼에도 불구하고, 당신은 당신이 아무것도 놓치지 않았다고 확신하지 못할 것입니다.
유일한 실제 솔루션 인 IMHO는 가능한 한주의를 기울이고, 원하는대로 자동화를 사용하고, 가능한 경우 리팩터링하며, 코드를 개선 할 수있는 방법을 지속적으로 찾는 것입니다. 물론, 어쨌든 그렇게하는 것이 좋습니다.
나는 그것을 직접 사용하지는 않았지만 cppcheck 는 사용되지 않는 기능을 찾는 다고 주장합니다. 완전한 문제를 해결하지 못할 수도 있지만 시작일 수도 있습니다.
Gimple Software에서 PC-lint / FlexeLint를 사용해 볼 수 있습니다. 그것은 주장
전체 프로젝트에서 사용되지 않는 매크로, typedef, 클래스, 멤버, 선언 등을 찾습니다.
나는 정적 분석을 위해 그것을 사용했고 그것을 매우 좋아한다는 것을 알았지 만 죽은 코드를 찾기 위해 그것을 사용하지 않았다는 것을 인정해야합니다.
사용하지 않는 물건을 찾는 일반적인 방법은
- 빌드 시스템이 종속성 추적을 올바르게 처리하는지 확인하십시오.
- 전체 화면 터미널 창이있는 두 번째 모니터를 설정하고 반복 빌드를 실행하고 첫 번째 화면 출력을 표시합니다.
watch "make 2>&1"
유닉스에서 트릭을 수행하는 경향이 있습니다. - 모든 줄의 시작 부분에 "//?"를 추가하여 전체 소스 트리에서 찾기 및 바꾸기 작업을 실행합니다.
- "//?"를 제거하여 컴파일러가 표시 한 첫 번째 오류를 수정하십시오. 해당 줄에.
- 남은 오류가 없을 때까지 반복하십시오.
이것은 다소 긴 과정이지만 좋은 결과를 제공합니다.
컴파일 오류를 발생시키지 않으면 서 많은 공용 함수 및 변수를 개인용 또는 보호 된 것으로 표시하십시오. 이렇게하는 동안 코드도 리팩토링하십시오. 기능을 비공개로 만들고 어느 정도 보호하면 검색 기능이 줄어 듭니다. 비공개 기능은 동일한 클래스에서만 호출 할 수 있기 때문입니다 (액세스 제한을 우회하는 어리석은 매크로 또는 기타 트릭이없는 경우가 아니라면 권장합니다) 새로운 직업을 찾으십시오). 현재 작업중 인 클래스만이 함수를 호출 할 수 있으므로 개인 함수가 필요하지 않다는 것을 결정하는 것이 훨씬 쉽습니다. 코드 기반에 작은 클래스가 있고 느슨하게 연결된 경우이 방법이 더 쉽습니다. 코드베이스에 작은 클래스가 없거나 결합이 매우 타이트한 경우 먼저 정리하는 것이 좋습니다.
다음으로 나머지 모든 공용 함수를 표시하고 클래스 간의 관계를 파악하기 위해 호출 그래프를 작성합니다. 이 나무에서 가지의 어떤 부분이 잘릴 수 있는지 알아 봅니다.
이 방법의 장점은 모듈 단위로 수행 할 수 있다는 점입니다. 따라서 코드 기반이 깨졌을 때 오랜 시간이 걸리지 않고 단위 테스트를 계속 통과 할 수 있습니다.
Linux를 callgrind
사용하는 경우 valgrind
제품군의 일부인 C / C ++ 프로그램 분석 도구 인 메모리 누수 및 기타 메모리 오류 (사용해야하는)를 검사하는 도구가 포함되어 있습니다. 실행중인 프로그램 인스턴스를 분석하고 호출 그래프 및 호출 그래프에서 노드의 성능 비용에 대한 데이터를 생성합니다. 일반적으로 성능 분석에 사용되지만 응용 프로그램에 대한 호출 그래프도 생성하므로 호출자와 함께 어떤 함수가 호출되는지 확인할 수 있습니다.
이것은 페이지의 다른 곳에서 언급 된 정적 메소드를 보완하는 것으로, 완전히 사용되지 않는 클래스, 메소드 및 함수를 제거하는 데 도움이됩니다. 실제로 호출되는 메소드 내에서 죽은 코드를 찾는 데 도움이되지 않습니다.
나는 실제로 그런 일을하는 도구를 사용하지 않았습니다 ...하지만 모든 대답에서 본 한, 아무도이 문제를 계산할 수 없다고 말한 적이 없습니다.
이것이 무엇을 의미합니까? 이 문제는 컴퓨터의 알고리즘으로 해결할 수 없습니다. 이 정리 (이러한 알고리즘이 존재하지 않음)는 Turing의 Halting Problem의 결과입니다.
사용할 모든 도구는 알고리즘이 아니라 휴리스틱입니다 (예 : 정확한 알고리즘이 아님). 사용되지 않은 모든 코드를 정확하게 제공하지는 않습니다.
한 가지 방법은 컴파일 중에 사용되지 않는 기계 코드를 제거하는 디버거 및 컴파일러 기능을 사용하는 것입니다.
일부 기계 코드가 제거되면 디버거에서 해당 소스 코드 행에 breakpojnt를 넣을 수 없습니다. 따라서 중단 점을 어디에나 배치하고 프로그램을 시작하고 중단 점을 검사하십시오. "이 소스에 대해로드 된 코드가 없음"상태 인 코드는 제거 된 코드에 해당합니다. 코드가 호출되지 않았거나 인라인되지 않았으며 최소값을 수행해야합니다. 이 두 가지 중 어느 것이 일어 났는지 찾아 내야합니다.
적어도 그것이 Visual Studio에서 작동하는 방식이며 다른 도구 세트도 그렇게 할 수 있다고 생각합니다.
많은 작업이지만 수동으로 모든 코드를 분석하는 것보다 빠릅니다.
응용 프로그램을 만드는 데 사용하는 플랫폼에 따라 다릅니다.
예를 들어 Visual Studio를 사용하는 경우 코드를 구문 분석하고 프로파일 링 할 수있는 .NET ANTS 프로파일 러 와 같은 도구를 사용할 수 있습니다 . 이런 식으로 코드의 어느 부분이 실제로 사용되는지 신속하게 알아야합니다. Eclipse에는 동등한 플러그인이 있습니다.
그렇지 않으면 최종 사용자가 실제로 사용하는 응용 프로그램 기능을 알아야하고 응용 프로그램을 쉽게 해제 할 수있는 경우 감사를 위해 로그 파일을 사용할 수 있습니다.
각 주요 기능에 대해 사용량을 추적 할 수 있으며 며칠 / 주 후에 해당 로그 파일을 가져 와서 살펴보십시오.
CppDepend 는 사용되지 않는 유형, 방법 및 필드를 감지하고 훨씬 더 많은 작업을 수행 할 수있는 상용 도구입니다. Windows 및 Linux에서 사용할 수 있지만 (현재 64 비트 지원은 없음) 2 주 평가판이 제공됩니다.
면책 조항 : 나는 거기에서 일하지 않지만이 도구에 대한 라이센스 ( .NET 코드의 더 강력한 대안 인 NDepend )를 소유 하고 있습니다.
궁금한 사람들을 위해 다음은 CQLinq로 작성된 데드 메소드를 감지하기위한 기본 제공 (사용자 정의 가능) 규칙 예제입니다 .
// <Name>Potentially dead Methods</Name>
warnif count > 0
// Filter procedure for methods that should'nt be considered as dead
let canMethodBeConsideredAsDeadProc = new Func<IMethod, bool>(
m => !m.IsPublic && // Public methods might be used by client applications of your Projects.
!m.IsEntryPoint && // Main() method is not used by-design.
!m.IsClassConstructor &&
!m.IsVirtual && // Only check for non virtual method that are not seen as used in IL.
!(m.IsConstructor && // Don't take account of protected ctor that might be call by a derived ctors.
m.IsProtected) &&
!m.IsGeneratedByCompiler
)
// Get methods unused
let methodsUnused =
from m in JustMyCode.Methods where
m.NbMethodsCallingMe == 0 &&
canMethodBeConsideredAsDeadProc(m)
select m
// Dead methods = methods used only by unused methods (recursive)
let deadMethodsMetric = methodsUnused.FillIterative(
methods => // Unique loop, just to let a chance to build the hashset.
from o in new[] { new object() }
// Use a hashet to make Intersect calls much faster!
let hashset = methods.ToHashSet()
from m in codeBase.Application.Methods.UsedByAny(methods).Except(methods)
where canMethodBeConsideredAsDeadProc(m) &&
// Select methods called only by methods already considered as dead
hashset.Intersect(m.MethodsCallingMe).Count() == m.NbMethodsCallingMe
select m)
from m in JustMyCode.Methods.Intersect(deadMethodsMetric.DefinitionDomain)
select new { m, m.MethodsCallingMe, depth = deadMethodsMetric[m] }
나는 그것이 자동으로 이루어질 수 있다고 생각하지 않습니다.
코드 적용 도구를 사용하더라도 실행할 충분한 입력 데이터를 제공해야합니다.
Coverity 또는 LLVM 컴파일러 와 같은 매우 복잡하고 가격이 높은 정적 분석 도구 가 도움이 될 수 있습니다.
그러나 확실하지 않으며 수동 코드 검토를 선호합니다.
업데이트
글쎄 .. 사용하지 않는 변수 만 제거하면 사용되지 않는 함수는 어렵지 않습니다.
업데이트
다른 답변과 의견을 읽은 후에는 할 수 없다는 것을 더 확신합니다.
의미있는 코드 적용 범위 측정을 위해서는 코드를 알아야하며, 수동 편집을 많이 수행하면 적용 범위 결과를 준비 / 실행 / 검토하는 것보다 빠릅니다.
나는 친구에게 오늘이 질문을 나에게 요청했고 , 데드 코드 섹션을 결정하기 위해 컴파일하는 동안 진행 중에 충분한 가시성을 가질 수있는 ASTMatcher 및 Static Analyzer 와 같은 유망한 Clang 개발을 둘러 보았습니다. 이것을 찾았습니다 :
https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables
참조되지 않은 심볼을 식별하기 위해 설계된 것처럼 보이는 몇 가지 GCC 플래그를 사용하는 방법에 대한 완전한 설명입니다!
어떤 함수가 호출 될지에 대한 일반적인 문제는 NP-Complete입니다. 튜링 기계가 멈추는 지 알 수 없으므로 어떤 기능이 호출되면 일반적인 방법으로 미리 알 수 없습니다. main ()에서 작성한 함수로가는 경로가 (정적으로) 있으면 얻을 수 있지만 그것이 호출 될 것을 보증하지는 않습니다.
g ++을 사용하면이 플래그를 사용할 수 있습니다-사용하지 않음
문서에 따르면 :
Warn whenever a variable is unused aside from its declaration, whenever a function is declared static but never defined, whenever a label is declared but not used, and whenever a statement computes a result that is explicitly not used.
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
편집 : 여기에 다른 유용한 플래그가 있습니다-접근 할 수없는 코드 문서에 따르면 :
This option is intended to warn when the compiler detects that at least a whole line of source code will never be executed, because some condition is never satisfied or because it is after a procedure that never returns.
참고 URL : https://stackoverflow.com/questions/4813947/how-can-i-know-which-parts-in-the-code-are-never-used
'development' 카테고리의 다른 글
가장 좋아하는 Django Tips & Features? (0) | 2020.03.11 |
---|---|
Android 애플리케이션의 빌드 / 버전 번호를 얻는 방법은 무엇입니까? (0) | 2020.03.11 |
Makefile.am과 Makefile.in은 무엇입니까? (0) | 2020.03.11 |
메모장에서 CRLF 찾기 ++ (0) | 2020.03.11 |
다른 html 양식 안에 html 양식을 갖는 것이 유효합니까? (0) | 2020.03.11 |