Code

#cpp #visual_studio 내가 warning level 4로 만들어 봐서 아는데

August 6, 2016

author:

#cpp #visual_studio 내가 warning level 4로 만들어 봐서 아는데

#visual_studio warning lv4

warning level 4로 만든 프로젝트는 총 3개다. 모두 덩치가 있는 게임 프로젝트였다. 크고 지루한 작업이었다. 하나를 제외하곤. 드물게 엔진이 level 4였다.

동료가 도와줘도 제안한 사람이 끌고 가야 한다. 혼자 한다는 생각을 하는 게 속 편하다. 도와주면 고마운 거고. 마라톤과 같은 작업. 최근에 level 4로 올리는 작업은 백그라운드로 1년 정도 진행했다. 멀리 보고 천천히 진행하자.

작업 준비

warning 증식을 우선 막는 게 첫 번째 작업이다.

  1. 헤더 파일을 하나 만들고 모든 프로젝트 precompiled header에서 include 한다. 예) disable_warning.h
  2. project > configuration properties > c/c++ > advanced > disable specific warnings 항목에 정의한 warning을 disable_warning.h 로 옮긴다. 코드에서 pragma로 warning을 비활성화한 것도 같이 옮긴다.
  3. 모든 프로젝트를 warning level 4로 올린다.
  4. treat warning as errors 항목을 yes로 설정한다.
  5. 빌드가 성공할 수 있게 disable_warning.h 항목에 발생하는 warning을 추가한다.

난 이렇게 시작한다. 1, 2, 5번 과정으로 warning을 한 곳으로 모은다. 한 곳에 모으고 하나씩 없애면서 고치면 된다. 3, 4번 과정으로 warning 증식을 막는다. warning을 고치고 있는데, 고쳐야 할 warning이 늘어나면 힘 빠진다. 눈 오는데, 눈 치우는 것처럼.

#pragma warning(disable:4706) // assignment within conditional expression

2, 5번 과정에서는 이렇게 코드로 warning을 끄면 된다.

모두 다 바로 고칠 필요는 없다

#pragma warning(push)
#pragma warning(disable:4706) // assignment within conditional expression
...
#pragma warning(pop)

warning을 고치면서 코드 결함을 만들면 안 된다. 당연한 얘기. 주의 깊게 읽어야 하는 코드라면 그냥 warning을 끄는 범위를 정의하고 넘어가자.

해당 코드를 더 잘 아는 사람에게 코드 리뷰를 보내면 된다. 아니면 저렇게 놔뒀다가 일이 손에 안 잡힐 때, 검색해서 하나씩 내가 고쳐도 된다. 그것도 아니면 저렇게 그냥 놔둬도 상관없다고 생각한다. warning 증식만 막는 거로도 충분하다.

level 4에서 나오는 warning 간단 메모

C4702 unreachable

// MSDN example
// C4702.cpp
// compile with: /W4
#include <stdio.h>

int main() {
  return 1;
  printf_s("I won't print.\n"); // C4702 unreachable
}

이 warning을 가장 먼저 잡는 걸 추천한다. 내가 level 4로 올리면서 찾은 버그 대부분이 4702였다. 루프를 도는데, 검사를 잘못해서 한 번도 안 돌고 무조건 종료한다든지 하는 버그를 찾을 수 있다.

C4706 assignment within conditional expression

int a = SomeFunc();
if (a = 1) { ... }

조건식 안에 할당이 있는 경우 발생한다.

if (1 = a) // compile error

이런 실수를 막으려고 상수를 앞에 넣는 컨벤션을 사용하기도 한다. warning 4706을 활성화 한다면 이런 컨벤션을 사용 안 해도 된다.

if (auto pA = GetA()) {
    ...
}

하지만 이런 스타일을 선호하는 프로그래머가 많으므로 토론 후 진행하는 걸 추천.

난 이런 스타일을 선호하는 프로그래머가 있다면 코드 정적 분석기를 믿고 warning 4706을 비활성화 한다. 정적 분석기로도 커버가 되기 때문에 무리하게 활성화할 필요는 없다고 생각한다. 정적 분석기가 없으면 상수를 lhs로 두는 컨벤션을 정하는 게 좋겠다.

C4189 local variable is initialized but not referenced

int ret = 0;
{
    ...
    int ret = 4;
}

SomeFunc(ret);

이것도 버그를 많이 찾을 수 있다.

C4100 unreferenced formal parameter

// MSDN example
// C4100.cpp
// compile with: /W4
void func(int i) {   // C4100, delete the unreferenced parameter to
                     //resolve the warning
   // i;   // or, add a reference like this
}

int main()
{
   func(1);
}

참조 안 하는 파라미터가 있을 때 나는 warning. UNREFERENCED_PARAMETER() 매크로를 쓰던지 직접 하나 만들어서 쓰면 된다.

귀찮더라도 활성화하는 걸 추천한다. 쓸데없이 넘기는 파라미터를 쉽게 발견할 수 있으며 함수 파라미터 개수를 줄이는 데 도움을 준다. 그리고 분명 파라미터를 써야 하는데, 비슷한 이름을 가진 멤버 변수를 사용하는 코드 결함도 발견할 수 있다.

assert() 함수처럼 특정 configuration에만 동작하는 매크로 때문에 귀찮은 일이 발생할 수는 있다. debug에서는 빌드 잘 되는데, release에서는 4100 warning을 내며 빌드 실패. 초반에만 깨지는 빌드를 자진해서 수정하며 팀원들에게 알리면 금방 다들 적응한다.

C4127 conditional expression is constant

assert(0), do {} while(0) 같은 게 터진다. 실패 전용 assert가 없다면 이참에 하나 만들자. assert부터 작업하는 게 쉽다.

#define ONCE() \
  __pragma( warning (push )) \
  __pragma( warning (disable :4127)) \
  while (0) \
  __pragma( warning (pop ))

난 싫어하지만 사용하는 동료가 많으면 ONCE() 매크로도 추가한다. #define 지시자(directive) 안에서는 __pragma 키워드를 사용하면 된다.

C4244 conversion from ‘type1’ to ‘type2’, possible loss of data

// MSDN example
// C4244_level4.cpp
// compile with: /W4
int aa;
unsigned short bb;
int main() {
  int b = 0, c = 0;
  short a = b + c; // C4244

  bb += c; // C4244
  bb = bb + c; // C4244
  bb += (unsigned short)aa; // C4244
  bb = bb + (unsigned short)aa; // OK
}

static_cast<> 줄기차게 쳐야 한다. 진지하게 vim을 배우는 걸 추천.

C4245 conversion from ‘type1’ to ‘type2’, signed/unsigned mismatch

// MSDN example
// C4245.cpp
// compile with: /W4 /c
const int i = -1;
unsigned int j = i; // C4245

static_cast<>, std::numeric_limits<> 사용해서 고친다. 컴파일 타임에 정의해야 하는 건 boost::integer_traits<> 사용

C4267 conversion from ‘size_t’ to ‘type’, possible loss of data

// MSDN example
// C4267.cpp
// compile by using: cl /W4 C4267.cpp
void Func1(short) {}
void Func2(int) {}
void Func3(long) {}
void Func4(size_t) {}

int main() {
  size_t bufferSize = 10;
  Func1(bufferSize); // C4267 for all platforms
  Func2(bufferSize); // C4267 only for 64-bit platforms
  Func3(bufferSize); // C4267 only for 64-bit platforms
  Func4(bufferSize); // OK for all platforms
}

겁나 고쳐야 한다. 엄청 지루한 작업. 명시적으로 캐스팅하던가 size_t 그대로 사용하게 수정. 32bit/64bit 빌드 테스트를 같이해야 한다.

C4714 marked as __forceinline not inlined

inline 키워드는 “이거 힌트니깐 inline 좀 해주세요”, __forceinline 키워드는 “이거 힌트니깐 inline 좀 해줘잉”. 애교가 통하는 사람이 있고 안 통하는 사람이 있다. 컴파일러도 마찬가지. __forceinline 키워드를 붙여도 inline 안 되는 경우가 있다. 그때 발생하는 warning. 그때 발생.

C4510 default constructor could not be generated

// MSDN example
// C4510.cpp
// compile with: /W4
struct A {
  const int i;
  int &j;
  A& operator=( const A& ); // C4510 expected
  // uncomment the following line to resolve this C4510
  // A(int ii, int &jj) : i(ii), j(jj) {}
}; // C4510

int main() {
}

상수나 참조를 멤버 변수로 가지고 있으면 발생. 선언 끝에 \=delete; 붙여서 삭제된 함수로 선언하거나 직접 구현하면 된다.

C4239 nonstandard extension used

값으로 리턴하는데, reference로 받거나 reference로 받는데, 인자를 바로 생성해서 넘기거나.

C4505 unreferenced local function has been removed

translation unit 단위로 사용 안 하는 static 함수가 있을 때, 발생한다. cpp에 정의한 건 죽은 코드이므로 지우면 되고 header에 static으로 정의한 함수가 있으면 inline 혹은 inline static으로 변경하면 된다.

C4130 logical operation on address of string constant

// MSDN example
// C4130.cpp
// compile with: /W4
int main()
{
  char *pc;
  pc = "Hello";
  if (pc == "Hello") // C4130
  {
  }
}

std::string을 사용하거나 strlen, strcmp와 같은 함수로 교체

C4996 This function or variable may be unsafe

msdn을 찾아보니 level 3으로 되어 있다. level 4로 올리면 발생하는 것도 있는 거로 봐서 depracated로 표시된 건 level 3으로 걸리나 보다.

주의해야 할 함수가 많으니 pragma로 감싸 놓고 차근차근 수정하는 걸 추천. 그냥 감싸놓고 앞으로 더 늘어나지만 않게 해도 괜찮다고 생각한다.

C4389 ‘operator’ : signed/unsigned mismatch

unsigned와 signed를 섞어 쓰는 걸 지극히 싫어한다. 하지만 STL은 unsigned로 정의한 size_t를 사용해서 이걸 피할 수도 없다. C4018은 int로 더하면서 size_t와 비교하는 for 루프에서 많이 발견된다. 이 warning도 근성.