사용하지 않는 코드를 제거 해야하는 레거시 C ++ 코드가 있습니다. 문제는 코드 기반이 크다는 것입니다.
어떤 코드가 절대 호출 / 사용되지 않는지 어떻게 알 수 있습니까?
f()
로 f()
모호하게 호출하면 두 번째 함수를 추가하는 것만으로는 해당 호출을 두 번째로 해결할 f()
수 없습니다. "세 번째 기능을 추가하면 호출이 모호 해져서 프로그램이 컴파일되는 것을 막을 수 있습니다. 반례를보기를 좋아할 것입니다.
사용하지 않는 코드를 제거 해야하는 레거시 C ++ 코드가 있습니다. 문제는 코드 기반이 크다는 것입니다.
어떤 코드가 절대 호출 / 사용되지 않는지 어떻게 알 수 있습니까?
f()
로 f()
모호하게 호출하면 두 번째 함수를 추가하는 것만으로는 해당 호출을 두 번째로 해결할 f()
수 없습니다. "세 번째 기능을 추가하면 호출이 모호 해져서 프로그램이 컴파일되는 것을 막을 수 있습니다. 반례를보기를 좋아할 것입니다.
답변:
사용되지 않는 코드에는 두 가지 종류가 있습니다.
첫 번째 종류의 경우 좋은 컴파일러가 도움이 될 수 있습니다.
-Wunused
(GCC, Clang )는 미사용 변수에 대해 경고해야하며, Clang 미사용 분석기는 읽지 않은 변수에 대해 경고하기 위해 증분되었습니다 (사용 되더라도).-Wunreachable-code
(이전 GCC, 2010 에서 제거됨 )는 액세스하지 않은 로컬 블록에 대해 경고해야합니다 (항상 true로 평가되는 초기 반환 또는 조건에서 발생)catch
컴파일러가 일반적으로 예외가 발생하지 않는다는 것을 증명할 수 없기 때문에 사용하지 않는 블록 에 대해 경고하는 옵션은 없습니다 .두 번째 종류의 경우 훨씬 더 어렵습니다. 정적으로 전체 프로그램 분석이 필요하며 링크 시간 최적화가 실제로 데드 코드를 제거 할 수는 있지만 실제로 프로그램은 수행 될 때 너무 많이 변환되어 의미있는 정보를 사용자에게 전달하는 것이 거의 불가능합니다.
따라서 두 가지 접근 방식이 있습니다.
gcov
. 컴파일 중에는 특정 플래그를 전달해야 제대로 작동합니다). 다양한 범위의 입력 (단위 테스트 또는 회귀 테스트가 아닌)으로 코드 범위 도구를 실행하면 데드 코드가 도달 할 수없는 코드 내에 있어야하므로 여기에서 시작할 수 있습니다.주제에 관심이 있고 실제로 도구를 직접 연습 할 시간과 성향이 있다면 Clang 라이브러리를 사용하여 그러한 도구를 만드는 것이 좋습니다.
Clang은 코드를 구문 분석하고 과부하 해결을 수행하므로 C ++ 언어 규칙을 처리 할 필요가 없으며 당면한 문제에 집중할 수 있습니다.
그러나 이러한 종류의 기술은 사용하지 않는 가상 재정의를 식별 할 수 없습니다. 이유는 신뢰할 수없는 타사 코드로 호출 할 수 있기 때문입니다.
foo()
만 표시 될 때 "호출"으로 표시되는 것을 방지 if (0) { foo(); }
하는 것은 보너스이지만 추가 스마트가 필요합니다.)
사용되지 않은 전체 함수 (및 사용되지 않은 전역 변수)의 경우 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 ++ 프로젝트 에서 비슷한 주제 데드 코드 감지를 발견 했습니다.
-Wunused
선언되었지만 실제로 사용되지 않은 변수에 대해 경고합니다. Clang에는 실험적 구현이 있습니다. Clang에는 (Ted Kremenek에 의해) 쓰여지지만 읽지 않는 비 휘발성 변수에 대해서도 경고합니다. -Wunreachable-code
도달 할 수없는 함수 내의 코드에 대해 경고합니다 . 예를 들어 절대 론적 인 경우에 발생하지 않는 분기에서 throw
or return
문 또는 코드 뒤에있는 코드 일 수 있습니다 .
코드 커버리지 도구를 찾고 있다고 생각합니다 . 코드 커버리지 도구는 코드가 실행되는 동안 코드를 분석하고, 실행 된 코드 줄과 몇 번, 그리고 어떤 코드 줄이 아닌지 알려줍니다.
이 오픈 소스 코드 커버리지 도구에 기회를 줄 수 있습니다 : TestCocoon -C / C ++ 및 C # 용 코드 커버리지 도구.
void func()
b.cpp에서 사용되는 a.cpp의 함수 를 보자. 컴파일러가 프로그램에서 func ()가 사용되는지 어떻게 확인할 수 있습니까? 링커 일입니다.
실제 답변은 다음과 같습니다. 절대 확실하게 알 수 없습니다.
적어도 사소한 경우에는 모든 것을 얻었을 수도 있습니다. 도달 할 수없는 코드에 대한 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 --enable=unusedFunction --language=c++ .
하지 않는 기능을 찾는 데 사용하십시오.
Gimple Software에서 PC-lint / FlexeLint를 사용해 볼 수 있습니다. 그것은 주장
전체 프로젝트에서 사용되지 않는 매크로, typedef, 클래스, 멤버, 선언 등을 찾습니다.
나는 정적 분석을 위해 그것을 사용했고 그것을 매우 좋아한다는 것을 알았지 만 죽은 코드를 찾기 위해 그것을 사용하지 않았다는 것을 인정해야합니다.
사용하지 않는 물건을 찾는 일반적인 방법은
watch "make 2>&1"
유닉스에서 트릭을 수행하는 경향이 있습니다.이것은 다소 긴 프로세스이지만 좋은 결과를 제공합니다.
컴파일 오류를 발생시키지 않으면 서 많은 공용 함수 및 변수를 개인용 또는 보호 된 것으로 표시하십시오. 이렇게하는 동안 코드도 리팩토링하십시오. 기능을 비공개로 만들고 어느 정도 보호하면 검색 기능이 줄어 듭니다. 비공개 기능은 같은 클래스에서만 호출 할 수 있기 때문입니다 (액세스 제한을 우회하는 어리석은 매크로 나 다른 트릭이없는 경우에는 권장됩니다) 새로운 직업을 찾으십시오). 현재 작업중 인 클래스만이 함수를 호출 할 수 있으므로 개인 함수가 필요하지 않다는 것을 결정하는 것이 훨씬 쉽습니다. 코드 기반에 작은 클래스가 있고 느슨하게 연결된 경우이 방법이 더 쉽습니다. 코드베이스에 작은 클래스가 없거나 결합이 매우 타이트한 경우 먼저 정리하는 것이 좋습니다.
다음으로 나머지 모든 공용 함수를 표시하고 클래스 간의 관계를 파악하기 위해 호출 그래프를 작성합니다. 이 나무에서 가지의 어떤 부분이 잘릴 수 있는지 알아 봅니다.
이 방법의 장점은 모듈 단위로 수행 할 수 있으므로 코드 기반이 깨졌을 때 오랜 시간이 걸리지 않고 단위 테스트를 계속 통과하기 쉽다는 것입니다.
Linux를 callgrind
사용하는 경우 valgrind
제품군의 일부인 C / C ++ 프로그램 분석 도구 인 메모리 누수 및 기타 메모리 오류 (사용해야하는)를 검사하는 도구가 포함되어 있습니다. 실행중인 프로그램 인스턴스를 분석하고 호출 그래프 및 호출 그래프에서 노드의 성능 비용에 대한 데이터를 생성합니다. 일반적으로 성능 분석에 사용되지만 응용 프로그램에 대한 호출 그래프도 생성하므로 호출자와 함께 어떤 함수가 호출되는지 확인할 수 있습니다.
이것은 페이지의 다른 곳에서 언급 된 정적 메소드를 보완하는 것으로, 완전히 사용되지 않는 클래스, 메소드 및 함수를 제거하는 데 도움이됩니다. 실제로 호출되는 메소드 내에서 죽은 코드를 찾는 데 도움이되지 않습니다.
나는 실제로 그런 일을하는 도구를 사용하지 않았습니다 ...하지만 모든 대답에서 본 한, 아무도이 문제를 계산할 수 없다고 말한 적이 없습니다.
이것이 무엇을 의미합니까? 이 문제는 컴퓨터의 어떤 알고리즘으로도 해결할 수 없습니다. 이 정리 (이러한 알고리즘이 존재하지 않음)는 Turing의 Halting Problem의 결과입니다.
사용할 모든 도구는 알고리즘이 아니라 휴리스틱입니다 (예 : 정확한 알고리즘이 아님). 사용되지 않은 모든 코드를 정확하게 제공하지는 않습니다.
한 가지 방법은 컴파일 중에 사용되지 않는 기계 코드를 제거하는 디버거 및 컴파일러 기능을 사용하는 것입니다.
일부 기계 코드가 제거되면 디버거는 해당 소스 코드 줄에 breakpojnt를 넣을 수 없습니다. 따라서 중단 점을 어디에나 배치하고 프로그램을 시작하고 중단 점을 검사합니다. "이 소스에로드 된 코드가없는"상태 인 코드는 제거 된 코드에 해당합니다. 해당 코드가 호출되지 않았거나 인라인되지 않았으며 최소한의 작업을 수행해야합니다. 이 두 가지 중 어느 것이 일어 났는지 찾아 내야합니다.
적어도 그것이 Visual Studio에서 작동하는 방식이며 다른 도구 세트도 그렇게 할 수 있다고 생각합니다.
많은 작업이지만 수동으로 모든 코드를 분석하는 것보다 빠릅니다.
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] }
응용 프로그램을 만드는 데 사용하는 플랫폼에 따라 다릅니다.
예를 들어 Visual Studio를 사용하는 경우 코드를 구문 분석하고 프로파일 링 할 수있는 .NET ANTS 프로파일 러 와 같은 도구를 사용할 수 있습니다 . 이런 식으로 코드의 어느 부분이 실제로 사용되는지 빠르게 알아야합니다. Eclipse에는 동등한 플러그인이 있습니다.
그렇지 않으면 최종 사용자가 실제로 사용하는 응용 프로그램의 기능을 알아야하고 응용 프로그램을 쉽게 해제 할 수있는 경우 감사를 위해 로그 파일을 사용할 수 있습니다.
각 주요 기능에 대해 사용량을 추적 할 수 있으며 며칠 / 주 후에 해당 로그 파일을 가져 와서 살펴보십시오.
나는 그것이 자동으로 이루어질 수 있다고 생각하지 않습니다.
코드 적용 도구를 사용하더라도 실행할 충분한 입력 데이터를 제공해야합니다.
Coverity 또는 LLVM 컴파일러 와 같은 매우 복잡하고 가격이 높은 정적 분석 도구 가 도움이 될 수 있습니다.
그러나 확실하지 않으며 수동 코드 검토를 선호합니다.
업데이트
글쎄 .. 사용하지 않는 변수 만 제거하면 사용되지 않는 함수는 어렵지 않습니다.
업데이트
다른 답변과 의견을 읽은 후에는 할 수 없다는 것을 더 확신합니다.
의미있는 코드 적용 범위 측정을 위해서는 코드를 알아야하며, 수동 편집을 많이 수행하면 적용 범위 결과를 준비 / 실행 / 검토하는 것보다 빠릅니다.
나는 친구에게 오늘이 질문을 나에게 요청했으며 , 데드 코드 섹션을 결정하기 위해 컴파일하는 동안 진행 중에 충분한 가시성을 가질 수있는 유망한 Clang 개발 (예 : ASTMatcher 및 정적 분석기) 을 살펴 보았습니다. 이것을 찾았습니다 :
https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables
참조되지 않은 심볼을 식별하기 위해 설계된 것처럼 보이는 몇 가지 GCC 플래그를 사용하는 방법에 대한 완전한 설명입니다!
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.