Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

소개

C언어를 이용해서 프로그램을 만드는 이유는 입력받은 데이터를 가공하거나 연산해서 결과물을 출력받기 위함이다. 이 데이터는 인간의 관점에서 보자면, 문자혹은 문자열일 수도 있고, 숫자일 수도 있다. 숫자라면 정수일 수도 있고, 소수점을 가진 숫자일 수도 있다. 혹은 이들 데이터의 묶음일 수도 있다.

컴퓨터는 모든 데이터를 bit 로 본다

그러나 인간이 데이터를 숫자, 문자, 문자열등 다양하게 구별하는 것과는 달리 컴퓨터는 모든 데이터를 bit로 본다. 숫자든 문자든 문자열이든지 간에 bit의 나열일 뿐이다. 컴퓨터는 bit이외의 다른 데이터는 알 수가 없다.

사람이라면 100000은 숫자고 사람은 문자 임을 경험적으로 알 수 있다. 그러나 컴퓨터는 경험이라는 걸 가지고 있지 못하다. 둘다 비트의 나열일 뿐이다. 컴퓨터는 이게 숫자인지 문자인지 구분하지 못한다. 0인지 1인지가 중요할 뿐이다.
00000000 1000011 11000000 00010000   <--- 100000
00100101 0011001 00010111 01100001   <--- "사람"

프로그램은 컴퓨터와 대화를 하는 객체다. 프로그램을 이용해서 우리는 덧셈/곱셈을 하거나 문자열을 처리하도록 해서 필요한 결과물을 얻어낸다. 그런데, 컴퓨터는 숫자와 문자를 구분하지 못하는데, 어떻게 프로그램의 요청을 처리할 수 있을까.

컴퓨터가 처리할 수 없으므로, 프로그램을 통해서 처리하는 수 밖에 없다. 즉 똑같은 00000110이라는 정보가 주어졌을 때, 어떨 때는 숫자로 처리하도록 하고, 어떨 때는 문자로 처리하도록 하는 일을 프로그램에서 맡아주어야 한다.

데이터 타입을 통한 추상화

컴퓨터가 0과 1만을 구분해 낼수 있다는 문제를 해결하기 위해서 프로그래밍 언어들은 데이터 타입이라는 것을 두어서 이문제를 해결한다.
     숫자 97          문자 'a' 
        ^                ^
        |                |
        +- 데이터 타입 --+         | 프로그래밍 언어 수준
               |                   |
            1100001                | 기계적 수준에서 본 숫자 97과 문자 'a'
위에서 처럼 컴퓨터에는 1100001 이라고 저장되어 있는 값을 프로그래밍 언어에서 어떤 데이터 타입으로 읽느냐에 따라서 숫자 97로 혹은 문자 'a'로 읽을 수도 있다.
#include <stdio.h>

int main()
{
	char id = 'a';

	printf("%d\n", (int)id); // 주 1
	printf("%c\n", id);

	printf("%c\n", (int)id + 2);
	printf("%c\n", 99); 
	return 0;
}
주 1에서 사용된 (int) 는 cast연산자로 변수id를 int형으로 읽으라는 걸 의미한다. 그러다 보니 문자와 숫자를 더하는 등의 주 2와 같은 연산도 가능해진다.
   00000000 00000000 00000000 1100001        <---- 'a'
 + 00000000 00000000 00000000 0000010        <---- '2'
 ------------------------------------
   00000000 00000000 00000000 1100011        <---- 문자 'c', 숫자로는 99 

숫자 다루기

숫자를 다루기 위한 데이터타입으로 int, long 형이있다는 걸 알고 있을 것이다. 우선 가장 많이 쓰이는 int에 대해서 알아보자. int는 정수를 나타내기 위해서 사용하며, 4 byte(:12)의 크기를 가지고 있다. 그러므로 최대 0~2^32 범위의 정수가 들어감을 알 수 있을 것이다.

그러나 정수에는 양의 정수와 함께 음의 정수가 있다. 이를 위해서 32bit의 첫 비트를 부호를 표시하기 위해서 사용하는데, 만약 첫 비트가 1이면 음의 정수 그렇지 않고 첫 비트가 0이면 양의 정수가 된다.
00000000 00000000 00000000 00000001    <--- 양의 정수 1
11111111 11111111 11111111 11111111    <--- 음의 정수 -1
개념은 쉽게 이해할 수 있을 것인데, 한가지 혼동되는 점이 있을 것이다. -1이면 100000000 00000000 ....이 아닌가? 하는 것이다. 여기에서 보수개념이 나온다. 보수는 다음 두 가지 방식이 있는데, 보수를 이용해서 음수를 표현하게 되는 매우 중요한 개념이니 숙지하고 넘어가도록 하자.
  • 1의 보수 : 양수의 비트를 모두 반전시키는 방법이다. 5는 00000101 이고 -5는 11111010이 된다. 단순하기는 하지만 이경우 +0과 -0이 존재한다는 단점을 가진다.
  • 2의 보수 : 1의 보수에 1을 더해서 음수를 표현한다. 모든 비트를 반전시킨 후 1을 더하면 된다. 이 방식에 따르면 -5는 11111011이 된다. 이 방식은 0이 하나만 존재하며, 모든 bit가 1이면 -1이 된다.
현대의 컴퓨터는 두개의 방식중에서 2의 보수의 방식만을 이용해서 음수를 표현한다. 다음 코드의 값을 예상해보고, 실행시킨 결과와 비교해 보기 바란다.
int main()
{
    int i = 1;
    int j;

    j = ~i;
    printf("%d\n", j); 
    printf("%d\n", ~j);
}
~보수연산자이다. 1은 00000000 00000000 00000000 00000001 이므로 j는 11111111 11111111 11111111 11111111 11111110이 되고, 이것은 -2가 됨을 알 수 있을 것이다. 만약 여러분이 양의 정수음의 정수로 혹은 그 반대로 바꾸고 싶다면 보수에 + 을 해주면 된다. 위 프로그램을 약간 수정해서 정수 변환 프로그램을 만들어 보기 바란다.

데이터 형변환

이상 컴퓨터는 모든 데이터를 bit로만 본다는 것을 이해했을 것이다. 이것을 인간이 보기 쉽게 일종의 약속을 통해서, 읽어들인 bit값을 숫자, 문자로 보게된다. 어차피 이것은 약속이기 때문에, 숫자나 문자 이외의 어떠한 데이터(구조체 같은)로도 가공할 수 있다.

그렇다면 각 데이터 타입을 서로 변환하는 것도 가능하리란걸 예상할 수 있을 것이다. int<->char, signed int <-> unsigned int, int <--> long 등의 데이터 타입간 변환이 가능하다. int형 데이터라 하더라도 char 데이터인것 처럼 읽자라고 약속만 하면 되기 때문이다.

이러한 약속을 가능하게 하는게 형변환연산자(캐스팅 연산자)이다.
#include <stdio.h>

int main()
{
  int i=1;
  unsigned int j=2;

  i = ~(i << 31);           // 주 1

  if (j < i+1)
    printf("i+1 > j \n");
  else
    printf("i+1 < j \n");
}
주 1은 int로 표현가능한 양의정수 중 가장 큰 수를 구하기 위한 코드다.
i << 31   은 10000000 00000000 00000000 00000000  
~(i << 31)은 01111111 11111111 11111111 11111111
이 되므로 가장큰 정수인 2147483647 이 된다.
여기에 +1 을 해서 2와 비교를 했다. 단순하게 생각하면 i는 2147483678 이 될 것이므로 j보다 크다라고 생각할 수 있겠지만 결과는 반대로 나올 것이다. 왜냐하면 i에 +1을 하면 100000000 00000000 00000000 00000000이 되는데, 이 것은 -2147483648이 되기때문이다.

이는 isigned 타입이라서 가장 왼쪽 비트가 음수를 나타내는데 사용되었기 때문이다. 이 문제는 i에 저장된 데이터를 unsigned 형으로 읽어라라고 해주는 것으로 해결할 수 있다. cast 연산자는 이럴 때 사용한다.
if (j < i+1) 을 
if (j < (unsigned int)i+1) 로 변경한다.
위에서 처럼 변경하고 컴파일 한 후, 다시한번 실행시켜 보면, 원하는 데로 결과가 나오는 것을 확인할 수 있을 것이다. 캐스트 연산자를 사용함으로써 가장왼쪽 비트를 음수를 나타내기 위한 표시가 아닌 값(2^31)으로 읽어들였기 때문이다.