티스토리 툴바

 

나 같은 경우 일단 돌아가는 코드를 만들고 돌아가는지 눈으로 확인한다음 손을 보는 스타일이라서, 나중에 많은 잔손질 - 거창하게 리팩토링 -을 하게 된다. 이때 모듈화와 함께 가장먼저 손쉽게 진행할 수 있는게 경고메시지를 제거하는 것이다. 이 문서는 gcc 4.0.x 를 기준으로 작성되었다.

gcc라면 다음과 같은 옵션을 이용해서 경고메시지를 출력하도록 할 수 있다.

# gcc -Wall -c testcode.c 
# gcc -W -c testcode testcode.c 
... 
 
보통은 -Wall 옵션만을 사용하는 경우가 많을 것이다. 그러나 경고메시지는 다양한 영역을 가지고 있으다. -Wall 의 all에 속지 말기 바란다. all이라는 단어가 붙은것 과는 다르게, 일부영역의 경고메시지만을 출력해준다. 예를 들면 아래와 같이 리턴값이 명시되지 않은 경우 경고메시지를 출력한다.
int foo (unsigned int x) 
{ 
  int y; 
  if (y < x) x=x+2; 
  else x= x+1; 
} 
 

-Wall 옵션을 주고 컴파일 해보자.

# gcc -Wall -c foo.c  
foo.c: In function `foo': 
foo.c:6: warning: control reaches end of non-void function 
 
그러나 if (y < x)에서 unsigned와 signed와의 비교와 같은 경고는 잡아내지 않는 것을 알 수 있다. 결국 모든 경고메시지를 없애고 싶다면, 다양한 종류의 경고옵션을 함께 사용해야 한다.
-W

signed 와 unsigned의 비교. 조건문에서 body가 명시되지 않거나, 결코도달할 수 없는 조건문등을 찾아낼 수 있다. 아래의 코드는 컴파일되고 실행되는데 전혀 문제는 없을 것이다. 그러나 if (x < 0) 문은 결코 만족할 수 없다. 이러한 코드는 버그를 만들어낼 수 있을 것이다.

int foo (unsigned int x) 
{ 
  if (x < 0) 
    return 0;  // 결코 도달할 수 없다.  
  else 
    return 1; 
} 
 
-Wall 옵션을 이용하면 경고메시지가 출력되지 않을 것이다.
# gcc -Wall -c foo.c 
 
그러나 -W 옵션을 이용하면 다음과 같은 경고메시지가 출력되는걸 확인할 수 있다.
# gcc -W -c foo.c 
foo.c: In function `foo': 
foo.c:4: warning: comparison of unsigned expression < 0 is always false 
 

일반적으로 -W 옵션은 -Wall 옵션과 함께 사용한다.

-Wconversion

이 옵션은 형변환(type conversion)과 관련되어서 잘못 사용된 코드에 대한 경고를 잡아낸다. floating-point와 integer, long과 short integers 사이의 형변환과 같은 것들이다. 예를 들어서 abs()는 실수형 정수에 대해서 절대값을 리턴하는 함수이다. 만약 인자로 float값을 입력한다면 원하지 않는 결과가 출력될 것이다.

#include <stdio.h> 
#include <stdlib.h> 
 
int main (void) 
{ 
  double x = -3.14; 
  double y = abs(x);  /* fabs(x)를 사용해야 함. */ 
  printf ("x = %g |x| = %g\n", x, y); 
  return 0; 
} 
 
-Wall 옵션을 주고 컴파일 하면, 경고메시지를 출력하지 않을 것이다. 그러나 프로그램을 실행시키면 잘못된 결과를 보여주게 된다.
# gcc -Wall -o abs abs.c 
# ./abs 
x = -3.14 |x| = 3 
 

이제 -Wconversion 옵션을 주고 실행시켜 보자.

# gcc -Wconversion -o abs abs.c 
abs.c: In function ‘main’: 
abs.c:7: warning: passing argument 1 of ‘abs’ as integer rather than  
    floating due to prototype 
 
abs() 함수대신에 fabs()함수를 이용하면, 위 코드의 논리적오류를 수정할 수 있다.

이 외에도 -Wconversion 옵션은 변수에 잘못된 값을 할당하는 오류도 찾아낸다.

unsigned int x = -1; 
 
기술적으로 ANSI/ISO C 표준은 위의 코드를 허용한다. 그러므로 대부분의 컴파일러가 에러없이 컴파일을 해준다. 그러나 코드가 제대로 작동할지는 보장할 수 없다.
-Wshadow

이것은 선언된 변수명을 다른 scope에서 다시 선언한 경우 경고를 발생시킨다. 이렇게 변수가 선언될 경우 이것을 shadowing변수라고 한다. 다음의 코드를 확인해 보도록 하자.

#include <stdio.h> 
double test (double x) 
{ 
  double y = 1.0; 
  { 
    double y; 
    y = x; 
  } 
  return y; 
} 
int main (void) 
{ 
  printf("%lf\n", test(5.0)); 
} 
 
2개의 scope에서 y변수가 선언되었음을 알 수 있다. 이경우 비록 이름은 같지만, scope가 다르므로 전혀다른 변수 이름 테이블을 가지게 된다. 당신이 사용하는 컴파일러가 ANSI/ISO C를 준수한다면, 1을 리턴할 것이다. shadowing 변수의 사용자체가 문제가 되는건 아니지만, 보통의 경우 실수로 사용하는 경우가 많으므로 -Wshadow 옵션을 이용해서 제거하도록 하자.
# gcc -Wshadow -o abs abs.c 
abs.c: In function ‘test’: 
abs.c:7: warning: declaration of ‘y’ shadows a previous local 
 
-Wcast-qual

const와 같은 타입 제한자를 잘못 사용했을 경우 경고메시지를 출력한다. 아래의 코드와 같은 경우다.

#include <stdio.h> 
 
void f (const char * str) 
{ 
  char * s = (char *)str; 
  s[0] = '\0'; 
} 
 
int main() 
{ 
  char *a = "hello World"; 
  f(a); 
} 
 
이 프로그램은 아무런 문제없이 컴파일 될 것이다. 그러나 실행시키면 segmentation fault를 출력하고 종료되어 버릴 것이다. 오히려 위의 경우는 프로그램이 죽어버림으로 즉시 문제를 찾을 수 있으니 큰 문제가 되지 않을 것이다. 다음과 같은 경우가 문제가 된다.
int main() 
{ 
  char a[] = "hello World"; 
  f(a); 
  printf("%s\n", a); 
} 
 
우리가 f 함수의 인자를 const 로 한것은 str이 변경되는걸 막기 위한 목적도 가지고 있을 것이다. 그런데 위의 경우 의도하지 않게 str이 변경되어 버리게 된다. -Wcast-qual 옵션을 이용하면 이러한 문제를 사전에 예방할 수 있다.
# gcc -Wcast-qual -o f f.c 
f.c: In function ‘f’: 
f.c:5: warning: cast discards qualifiers from pointer target type 
 
-Wtraditional

대부분의 컴파일러는 ANSI/ISO 표준을 따르지만, 좀 더 유연하게 사용할 수 있도록 확장된 부분이 있다. 이 옵션을 주면 ANSI/ISO의 표준을 엄격하게 검사하고 이에 대한 경고메시지를 출력한다. 이 기종간 호환되는 코드를 작성하고자 한다면 고려해볼만 하다.

경고메시지 제거

이제 위에 언급된 옵션을 이용해서 parse.cc를 컴파일 해보도록 하자. parse.cc는 두번의 리팩토링 과정이 끝난 코드로 리팩토링 - 모듈화문서의 코드를 사용할 것이다.

# g++ -W -Wall -Wcast-qual -Wshadow -o parse parse.cc 
parse.cc: In function `char* html_trim(char*, char*, int)': 
parse.cc:54: warning: unused variable `char specia[10]' 
parse.cc: In function `int main(int, char**)': 
parse.cc:87: warning: declaration of `lt_flag' shadows a global declaration 
parse.cc:22: warning: shadowed declaration is here 
parse.cc:83: warning: unused variable `int ridx' 
parse.cc:84: warning: unused variable `int widx' 
parse.cc:87: warning: unused variable `int lt_flag' 
parse.cc:96: warning: unused variable `int tag_status' 
 
대부분 선언만 하고 사용하지 않는 변수에 관한 경고이고, shadows 변수사용에 따른 경고가 발견되었다.

다음은 경고가 발생한 부분을 모두 정리한 코드다.

#include <stdio.h> 
#include <sys/types.h> 
#include <string.h> 
#include <unistd.h> 
#include <fcntl.h> 
#include <sys/stat.h> 
#include <stdlib.h> 
 
using namespace std; 
 
const int MAX_BUF_SIZE=1024; 
 
enum tag_token {TAG_LT = '<', TAG_GT='>'}; 
 
void help() 
{ 
  printf("Usage : ./parse [-h] [-f FILENAME]\n"); 
  printf("-f : Input Source File\n"); 
  printf("-h : This Message\n"); 
} 
 
int lt_flag = 0; 
 
char *html_trim(char *src, char *dst, int size) 
{ 
  int ridx = 0; 
  int widx = 0; 
 
  while(ridx < size) 
  { 
    if (src[ridx] == TAG_LT) 
    { 
      lt_flag++; 
    } 
    if (src[ridx] == TAG_GT) 
    { 
      lt_flag--; 
      if (lt_flag == 0) 
      { 
        ridx++; 
        continue; 
      } 
    } 
 
    if (lt_flag == 1) 
    { 
      ridx++; 
      continue; 
    } 
 
    if(src[ridx] == '&') 
    { 
      unsigned int i = 0; 
      char specia[10] = {0x00,}; 
      for (i = 0; i < 10; i++) 
      { 
        if (src[ridx+i] == ';') 
        { 
          ridx= (ridx+i); 
          break; 
        } 
      } 
    } 
 
    if (src[ridx] == '\n' || src[ridx] =='\r') 
    { 
      dst[widx] = ' '; 
    } 
    else 
    { 
      dst[widx] = src[ridx]; 
    } 
    widx++; 
    ridx++; 
  } 
  return dst; 
} 
 
int main(int argc, char **argv) 
{ 
  int fd; 
  int readn; 
  int ridx; 
  int widx; 
  char rbuf[MAX_BUF_SIZE] = {0x00,}; 
  char wbuf[MAX_BUF_SIZE] = {0x00,}; 
  int lt_flag = 0; 
  int test_argv=0; 
  int c; 
  char *src_file; 
 
  // used tokenizing 
  char seps[] = "()|{}, \t.;&-[]\"\':`+#="; 
  char *tr; 
 
  int tag_status = 0; 
  int offset = 0; 
 
  while((c = getopt(argc, argv, "hf:")) != -1) 
  { 
    switch(c) 
    { 
      case 'h': 
        break; 
      case 'f': 
        src_file = optarg; 
        test_argv = 1; 
        break; 
      default: 
        break; 
    } 
  } 
 
  if (!test_argv) 
  { 
    help(); 
    return 1; 
  } 
 
 
  if ((fd = open(src_file, O_RDONLY)) < 0) 
  { 
    return 1; 
  } 
 
  while ((readn = read(fd, rbuf+offset, MAX_BUF_SIZE)) > 0) 
  { 
    html_trim(rbuf, wbuf, readn); 
    tr = strtok(wbuf, seps); 
    while (tr != NULL) 
    { 
      printf("%s ", tr); 
      tr = strtok(NULL, seps); 
    } 
    memset(rbuf, 0x00, MAX_BUF_SIZE); 
    memset(wbuf, 0x00, MAX_BUF_SIZE); 
  } 
  return 0; 
} 
 

Joinc Wiki: 리팩토링 - 모든 경고메시지를 체크하라.

Trackback 0 | Comment 0

코이림's Blog is powered by Daum & tistory