336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

1 Data Types

우리가 사용하는 데이터는 컴퓨터의 메모리에 연속된 비트의 나열로 저장이 된다. 이러한 비트의 나열은 컴퓨터입장에서는 문제가 안 되겠지만 인간의 입장에서는 알아보기 힘들다는 문제가 발생한다. 그래서 데이터 타입을 두어서 인간이 좀더 쉽게 사용할 수 있도록 하고 있다.

데이터 타입은 다음과 같은 특징을 가지고 있다.
  • 데이터가 어떻게 표현되고 사용 될지를 결정한다.
  • 데이터 타입에 따라 컴퓨터가 어떻게 데이터를 다룰지를 알려주다. (숫자로 다룰지 아니면 문자로 다룰지 등..)
  • 모든 값은 데이터 타입에 의해서 표현될 수 있다.

예를 들어서 메모리에 다음과 같은 비트패턴이 저장되어 있다고 가정해보자.
  0000 0000 0110 0111 
 

이 값이 어떻게 표현될까 ? 이것은 데이터 타입을 어떻게 정의하느냐에 따라 달라진다. 만약 데이터 타입을 'int로 하기로 했다면 숫자 103으로 표현될 것이다. 그러나 문자를 저장하는 char로 하기로 했다면 영문자 g로 표현이 된다.

데이터 타입은 이렇게 인간의 입장에서 컴퓨터 메모리에 저장된 데이터를 어떻게 다룰 것인지를 결정하기 위해서 사용이 된다.

아래의 코드를 실행시켜보기 바란다.
#include <stdio.h> 
 
int main(int argc, char **argv) 
{ 
  int a = 103; 
  char b = 103; 
 
  printf("%d\n", a); 
  printf("%c\n", b); 
} 
 
똑같은 103인데, 서로 다르게 출력되는걸 확인할 수 있을 것이다.

2 Primitive Data Types

인간은 매우 다양한 형태의 데이터를 다루기를 원하고, 그런일을 할 수 있는 프로그램을 만들어 낼 수 있어야 한다. 다루는 데이터의 형태가 다양하니, 데이터 타입 역시 다양하면 좋을 것이다. 그러나 컴퓨터는 매우 단순한 기계다. 쓸데없이 데이터 타입을 이것 저것 만들면, 이것을 다루는 컴퓨터 역시 달가워하지 않을 것이다. 프로그래머 역시 각 데이터 타입에 따른 고려사항이 늘어나니 권장할만한 사항이 아니다.

그래서 데이터타입을 다루어야 하는 프로그래밍 언어는 Primitive Data Type이라고 불리우는 최소한의 반드시 필요한 데이터 타입을 지원하고 있다. Primitive Data Type은 원시 데이터 타입 이라고 부르기도 한다.

각 원시 데이터 타입은 고유의 크기를 가지고 있으며, 표현할 수 있는 데이터의 한계가 정의되어 있다. 다음은 C 언어에서 지원하는 원시 데이터 타입과 크기, 데이터 범위를 보여주는 표다.

2.1 숫자형 원시 데이터 타입

숫자를 표현하기 위해서 사용되는 데이터 타입이다. 크게 정수형 데이터를 표현하기 위한 정수형 원시 데이터 타입부동소숫점형 원시 데이터 타입으로 나눌 수 있다.
  • 정수형 원시 데이터 타입
    char 1byte -128 127
    short 2byte -32768 32767
    long int 4byte 2,147,483,648 2,147,483,647
    long long int 8byte 9,223,372,036,854,775,808 9,223,372,036,854,775,807

이들 정수형 데이터 타입들은 signed비트라는 것을 가지고 있어서, 음수까지 표현할것인지를 정의할 수 있다. 음수까지 표현할 거라면 signed를 양수만 표현할거라면 unsigned를 타입의 앞에 붙여준다. 따로 붙여주지 않았다면 signed 가 붙은걸로 해석을 한다. 즉 위의 데이터 타입은 실제로는 signed char, signed short, signed long int, signed long long int와 동일하다.

unsigned를 명시하게 되면 양의 정수만 표현하게 된다. 음의 정수를 표현할 필요가 없으니 그만큼 양수 쪽으로 표현범위가 늘어날 것이다. unsigned char 이라면 255, unsigned long int 라면 4,294,967,295 가 된다.

  • 부동소숫점형 원시 데이터 타입
    float 4byte +/- 10E-37 +/- 10E38
    double 8byte +/- 10E-307 +/- 10E308

2.2 enumerated type

일반적으로 enumerated 타입은 숫자로 나열된 카테고리 같은 데이터를 만들기 위해서 사용한다. C에서는 enum을 이용해서 enumerated 타입의 데이터를 정의할 수 있다.
enum cardsuit { 
   CLUBS, 
   DIAMONDS, 
   HEARTS, 
   SPADES 
}; 
 
이제 enum 을 이루는 각각의 요소들은 숫자 0,1,2... 로 차례대로 대응되게 된다. 다음의 프로그램을 실행시켜 보자.
#include <stdio.h> 
enum cardsuit { 
  CLUBS, 
  DIAMONDS, 
  HEARTS, 
  SPACES 
}; 
int main() 
{ 
  printf("Card CLUBS is %d\n", CLUBS); 
  printf("Card DIAMONDS is %d\n", DIAMONDS); 
  printf("Card HEARTS is %d\n", HEARTS); 
  printf("Card SPACES is %d\n", SPACES); 
} 
 

다음과 같은 결과를 확인할 수 있을 것이다.
# ./enum 
Card CLUBS is 0 
Card DIAMONDS is 1 
Card HEARTS is 2 
Card SPACES is 3 
 

그렇지만 CLUBS 가 0이 아닌 다른 수로 대응되어야 할 경우도 생길 것이다. 그럴 땐, 필요한 값을 대입시켜 주면 된다. 즉 값을 명시하지 않으면 0부터 시작해서 1씩 증가하고, 값을 명시하면 명시된 값을 시작으로 1씩 증가하는 것으로 보면 된다.

문제
cardsuit를 다음과 같이 정의 했을 때, 어떤 값이 출력될지 생각해 보라.

enum cardsuit { 
  CLUBS = 1, 
  DIAMONDS, 
  HEARTS = 100, 
  SPACES 
}; 
 

2.3 Pointer type

컴퓨터는 계산을 하기 위한 기계다. 이때 계산에 사용될 모든 데이터는 일단 메모리로 읽혀져서 필요한 계산을 하게 된다. 예를 들어 하드디스크에 A 라는 문서가 있다고 가정해 보자. 이 문서를 편집하기 위한 프로그램을 가동시키면, 프로그램은 하드디스크에 있는 문서를 모두 읽어서 컴퓨터의 메모리로 불러들인 다음 필요한 일을 하게 된다. 다른 모든 연산들역시 마찬가지다.

그렇다면 문제가 발생한다. 컴퓨터에는 여러개의 프로그램이 떠 있을테니, 각각의 프로그램이 사용하는 데이터가 메모리의 여기저기 저장되어 있을 것이다. 이때 프로그램은 자신이 사용할 데이터가 메모리상의 어느 위치에 있는지 알고 있어야 한다.

이를 위해서 메모리에는 아래 그림과 같이 주소 값이 부여되어 있다.

pointer.png

프로그램은 이 기억값을 기억해서 데이터의 위치를 정확히 찾아내어서 읽어오게 되는 것이다. Pointer는 이러한 주소값을 저장하는 데이터 타입이다.

다른 데이터 타입들이 그렇듯이 pointer도 고유의 크기를 가지고 있다. pointer 데이터 타입의 크기는 4byte이다. 메모리상의 주소에는 마이너스 값이 필요가 없으므로 0 - 4,294,967,295의 숫자가 저장된다. 32bit 컴퓨터에 사용가능한 메모리의 총크기가 4Giga다 라는 얘기가 여기에서 나온다. 가리킬수 있는 숫자의 범위가 4,294,967,295 (약 4giga)이므로, 이를 초과한 영역에 저장된 데이터는 읽어올 수가 없기 때문이다.

이는 C 와 같은 프로그래밍 언어에도 그대로 적용된다. 포인터의 크기가 4byte 이니, 최대다룰 수 있는 메모리의 크기가 4Giga로 제한이 된다. 물론 여러분이 64bit 컴퓨터운영체제 그리고 컴파일러를 사용한다면 테라 byte급의 데이터를 다룰 수 있다. 64bit 컴퓨터가 대용량 데이터 처리에 유리하다는 얘기가 나오는 이유다.

포인터는 이쯤에서 끝내도록 하겠다. 포인터에 대한 자세한 내용은 따로 한장 정도를 할애해서 자세히 다루도록 하겠다.

3 type casting - 형변환

아래 프로그램을 컴파일 후 실행시켜 보자.
#include <stdio.h> 
 
int main(int argc, char **argv) 
{ 
  unsigned char ch = 'c'; 
 
  printf("%c\n", ch); 
  ch = ch+1; 
  printf("%c\n", ch); 
  ch = ch+1; 
  printf("%c\n", ch); 
} 
 
결과로 c d e 가 출력될 것이다.

이상하군. 분명히 두개는 서로 다른 데이터 타입인데, 더하기가 되는군 ?

앞서 언급했지만, 데이터형이란 표현방식에 따른 것일 뿐이다. 컴퓨터 입장에서는 모두가 비트일 뿐이다. 즉 ch+1은 컴퓨터 입장에서 다음과 같이 계산이 된다.
                               0110 0011   = 'a' 
 0000 0000 0000 0000 0000 0000 0000 0001   = '1' 
 ========================== 
 0000 0000 0110 0100   = 'b' 
 
만약 printf("%d\n" ch+1) 을 한다면, 100 이 출력될 것이다. 비트패턴을 숫자로 표현하도록 표현방식을 바꿨기 때문이다.

이렇게 모두 동일한 비트일 뿐임으로 서로 다른 타입간의 계산이 가능해진다. 그러나 이건 어디까지나 가능하다 일뿐 실제로는 의도하지 않는 다양한 문제가 발생할 수 있다.

  • 데이터타입의 크기에 따른 문제
    데이터 타입은 서로 다른 크기를 가지고 있다. 위의 코드에서 ch 에 1000을 더하면 어떻게 될까. 1099가 나오길 예상할 수 있겠지만 ch의 테이터 타입인 char는 1byte의 크기로 255까지만 표현이 가능하다. 때문에 데이터 저장공간을 초과하게 될 것이다. 실제로는 1byte의 상위 비트는 버려지게 된다. 고로 0-255까지의 값이 출력이 될것이다. 뭐.. 값이 255를 초과하지 않는 범위에서 연산을 한다면 문제가 없기는 하겠지만 실수로 문제가 발생할 소지가 다분하다.

    다른 데이터 타입끼리라도 주의해서 프로그래밍을 하면 문제 없겠지만 가능하면, 타입을 맞추어서 계산을 하는게 좋다.

  • signed bit 문제
    정수형 데이터 타입은 signed bit 를 가지고 있어서 이걸로 양수인지 음수인지를 판단하게 된다. 다음의 코드를 확인해 보자.

    #include <stdio.h> 
     
    int main(int argc, char **argv) 
    { 
      unsigned int i = 100; 
     
      if (i < -10) 
      { 
        printf("Large\n"); 
      } 
    } 
     
    상식적으로 100 은 -10보다 크기 때문에 if문의 블럭은 실행되지 않아야 겠지만, 컴파일해서 실행시켜 보면 블럭문이 실행이 되는걸 알 수 있다. 이는 i 가 unsigned 형으로 -10 을 unsigned 형으로 보고 비교를 하기 때문이다. -10이 unsigned 형이 되면 4,294,967,285 으로 표현이 된다. 이를 이해하기 위해서는 2의 보수를 통한 singed 데이터 처리에 대해서 알고 있어야 하는데, 5장 데이터와 비트문서를 읽어보기 바란다.

    이 문제를 해결하기 위해서 형변환을 수행한다. 위의 프로그램의 경우 문제를 피해가는 가장 좋은 방법은 i를 signed 형으로 선언하는게 될것이다. 그러나 불가피 하게 unsigned 형을 고집해야 할 경우가 발생한다. 그때는 casting(형변환)연산자를 통해서 형변환을 하도록 한다.

    if ((signed)i < -10) 
    { 
    ... 
    } 
     
    이제 제대로 되는걸 확인할 수 있을 것이다.

프로그램을 작성할 때는 데이터가 어디에 쓰일 것인지를 명확히 해서, 그에 맞는 데이터 타입을 지정해 줘야 한다. 그렇지만 사람이다 보니, 위에서 처럼 사소한 실수를 하기도 한다. 문제는 이런 프로그램도 문제 없이 컴파일이 된다는 것이다. 결국 프로그램이 실행하는 도중에 문제를 일으키게 될 것이다. 다행히 gcc 컴파일러는 컴파일 옵션을 통해서 저러한 문제를 사전에 잡아낼 수 있게 하고 있다. 위 프로그램을 type.c로 저장하고 아래와 같은 옵션을 주고 컴파일 해보자.
# gcc -W -Wall -o type type.c 
... 
type.c:7: warning: comparison between signed and unsigned 
... 
 
comparison between signed and unsigned와 같은 경고메시지를 출력함을 알 수 있다. 자세한 내용은 리팩토링 : 모든 경고메시지를 제거하라 문서를 읽어보기 바란다. 아직 읽기 버겁다면, 그냥 대충 저런게 있나보다 하는 수준에서 읽어두어도 도움이 될 것이다.

4 문자와 문자열 표현

데이터는 크게 문자와 숫자로 이루어져 있음을 알고 있다. 그런데 정작 문자문자열을 표현하기 위한 데이터 타입을 다룬거 같지를 않다. 이에 대해서 얘기해 보고자 한다.

C 언어에서 문자를 위한 데이터 타입으로는 char 데이터 타입을 사용한다. char 는 1byte 256의 크기를 가지는데, 1byte 문자권의 영어와 수십개의 특수문자를 충분히 표한할 수 있는바 char를 문자를 저장하기 위한 데이터 타입으로 사용하고 있다.

컴퓨터에서 표현되는 문자는 0에서 255까지의 각 크기에 대응되는 문자들이 표준으로 정의되어 있다. 각 값에 대응되는 문자는 ASCII 테이블로 정리되어 있다.

http://www.joinc.co.kr/albums/album01/age.gif

http://www.joinc.co.kr/albums/album01/agf.gif

그러나 ASCII 테이블만 가지고는 일본어, 한글, 중국어와 같은 2byte 문자는 표현할 수 없다. 2byte 문자는 char를 2개 이상 사용해서 저장해야 한다.

이제 마지막으로 문자열이 남았다. C는 문자열을 위한 데이터 타입을 가지고 있지 않다. C에서 문자열을 처리하기 위해서는 배열을 사용해야만 한다. 이것은 원시 데이터 타입을 여러개를 포함하고 있는 데이터 구조다. 예를 들어 문자열은 char를 여러개 포함할 수 있는 데이터 구조를 이용하면 표현할 수 있을 것이다. 배열은 다음장에서 자세히 다루도록 하겠다.

5 typedef 를 이용한 타입 재정의

데이터 타입은 타입에 맞는 고유한 이름을 가지고 사용하게 된다. 그런데 타입의 이름이 너무 길 경우, 이를 이용해서 프로그램을 작성할 경우 불편한 점이 발생한다. 다음의 경우를 보자.
int main() 
{ 
  unsigned long int a; 
  unsigned long int b; 
  unsigned long long c; 
  ... 
} 
 
사용하는데 문제는 없겠지만, 저렇게 긴 타입의 변수가 여기저기에 생성된다면 가독성이 떨어지는 지저분한 코드가 만들어질 가능성이 있다. 이럴경우 typedef 를 이용해서 기존의 데이터 타입을 다른 짧은 이름으로 재정의 해서 사용할 수 있다. typedef는 다음과 같이 사용할 수 있다.
typedef [원래 데이터 형] [재정의될 데이터 타입의 이름] 
 
typedef를 이용하면, 다음과 같이 코드를 깔끔하게 만들 수 있다.
typedef unsigned long int uint; 
typedef unsigned long long ulint; 
 
int main() 
{ 
    uint a; 
    uint b; 
    ulint c; 
} 
 

6 문제

  1. char 데이터 타입을 이용해서 hello world를 화면에 출력해 보자.
    • char를 여러개 써야함.
    • printf 를 통해서 출력할 수 있음.
  2. char 데이터 타입을 이용해서 hello world를 출력해보자. 단 정수형 숫자를 사용해야 한다.
    • ASCII 테이블을 이용하면 됨.

+ Recent posts