
1. 자료형

C언어에서 이렇게 많은 수의 자료형을 제공하는 이유가 있다.
"첫째, 데이터의 표현방식이 다르므로, 최소 둘 이상의 자료형이 필요해서"
자료형의 종류는 크게 정수 자료형과 실수 자료형으로 나뉜다. 이렇게 두 가지로 나눠놓은 이유는 컴퓨터가 정수와 실수를 표현하는 방식이 다르기 때문이다. 따라서 정수를 표현하기 위한 정수 자료형과 실수를 표현하기 위한 실수 자료형이 최소한 하나씩은 있어야 한다.
"둘째, 메모리 공간의 적절한 사용을 위해서 다양한 크기의 자료형이 필요해서"
예를 들어 총 5000개의 정수를 저장해야 한다고 가정해보자. 이 정수들을 short형으로 표현해서 저장한다면, 총 5000 x 2 = 10000 바이트가 소모된다. 반면 이를 int형으로 표현해서 저장한다면, 이의 두 배에 해당하는 바이트 수가 소모되어 그만큼 메모리를 낭비하는 결과로 이어질 수 있다. 즉, 메모리의 효율적 사용을 위해서 다양한 크기의 자료형이 존재하는 것이다.
sizeof(int); //4
sizeof(double); //8
sizeof 연산자를 이용하면 자료형의 크기를 확인할 수 있다.
sizeof 연산자는 피연산자를 소괄호로 감싸기 때문에 함수로 오인하는 경우가 있다.
sizeof는 함수가 아닌 연산자이다.
2. 정수를 표현하기 위한 일반적인 자료형의 선택
char num1=1, num2=2;
short num3=1, num4=2;
printf("%d", sizeof(num1+num2));
위 코드에서 출력되는 값이 1일 것 같지만(둘 다 char형이므로 연산의 결과 값도 char형이라서 sizeof(char)를 하면 1이니까) 4가 출력된다.
일반적으로 CPU가 처리하기에 가장 적합한 크기의 정수 자료형을 int로 정의한다.
따라서 int형 연산의 속도가 다른 자료형의 연산속도에 비해서 동일하거나 더 빠르다.
이렇듯 int형 연산이 CPU가 성능을 내기에 가장 좋은 연산이다 보니, int보다 작은 크기의 데이터는 int형 데이터로 바꿔서(이를 가리켜 '형 변환'이라 한다) 연산이 진행된다.
"그럼 char형 변수나 short형 변수는 불필요한 것인가?"
아니다. 이들 역시 유용하게 사용된다.
데이터의 양이 많아서 연산속도보다 데이터의 크기를 줄이는 것이 더 중요한 데이터들은 char형, short형 변수가 유용하게 사용된다.
3. 실수를 표현 및 처리하기 위한 일반적인 자료형의 선택
실수 자료형의 선택에 있어서 가장 중요한 요소는 '정밀도'이다.
여기서 말하는 정밀도는 '오차가 발생하지 않는 소수점 이하의 자릿수'를 뜻한다.
오차는 데이터 표현에 사용되는 바이트 수가 커지면 줄어들기 마련이다.

정수 자료형에서 int를 보편적으로 선택하듯이, 실수 자료형에서는 double을 주로 사용한다.
double num = 12.34;
printf("%f", num);
scanf("%lf", &num);
double형 데이터를 출력할 때에는 서식문자 %f를 사용하지만, double형 데이터를 입력 받을 때에는 서식문자 %lf를 사용한다.
4. unsigned 자료형
정수 자료형에 한해서 unsigned 선언을 추가하면, 0 이상의 값만 표현하는 자료형이 되어서, 표현할 수 있는 값의 범위가 양의 정수 방향으로 두 배 더 넓어지게 된다.
예를 들어서 char형 변수의 경우, 기본적으로 표현할 수 있는 값의 범위가 -128 이상 +127 이하이다. 그러나 앞에 unsigned를 붙여 unsigned char가 되면 표현할 수 있는 값의 범위가 0 이상 +255 이하가 되는 것이다.
이렇게 가능한 이유는 부호를 표현하는 MSB조차 값의 크기를 나타내는 비트로 사용되기 때문이다.
char형은 예외일 수 있다.
다른 정수 자료형들과 달리 char는 signed char와 다른 선언일 수도 있다.
char를 unsigned char로 처리하는 컴파일러도 존재하기 때문이다.
이런 이유로 char형 변수를 선언해서 음의 정수를 저장하는 경우에는 signed 선언을 추가하기도 한다.
5. 문자의 표현을 위한 아스키(ASCII) 코드

char ch = 'A';
문자는 작은 따옴표(')로 감싸서 표현한다.
그리고 이렇게 표현된 문자는 컴파일러에 의해서 다음과 같이 변환이 된다.
char ch = 65;
즉, 컴파일러도 아스키 코드를 알고 있다.
따라서 프로그래머가 표현해 놓은 문자를 컴퓨터가 인식할 수 있도록 숫자로 바꿔버린다.
char ch = 'A';
printf("%c", ch);
문자를 표현할 때는 %c 서식문자를 사용한다.
정수는 출력의 방법(%d or %c)에 따라서 문자의 형태로도, 숫자의 형태로도 출력이 가능하다.
위의 아스키 코드 표를 봐서 알겠지만, 아스키 코드 값은 0이상 127이하로 이뤄져 있다.
따라서 char형 변수로 충분히 저장이 가능하다.
int형 변수에도 저장은 가능하지만 메모리의 효율적 사용을 위해서라도 굳이 그렇게 할 필요가 없다.
조금 더 정확히 설명한다면, char형은 문자의 표현을 위해서 정의된 자료형이다.
그래서 이름도 character의 앞 네 글자를 따서 char로 정한 것이다.
그런데 이와 관련해서 다음과 같이 질문하는 분도 계실 것이다.
"int형으로 선언해야 빠르다면서요, 그럼 문자도 int형 변수에 저장해야 하는 것 아닌가요?"
매우 좋은 질문이다.
그렇다면 int형으로 선언했을 때 무엇이 빨라진다고 헀는지 상기해보자.
그것은 '연산'이다.
즉, 정수형 연산을 할 때 빠르다고 하였다.
그렇다면 char형 변수에 저장된 문자를 가지고 덧셈이나 뺄셈같은 연산을 할 일이 있겠는가?
따라서 종합적으로 판단해 볼 때, 문자를 저장하는 데에는 int형 변수보다 char형 변수가 더 적합하다.
6. 상수에 대한 이해
이번엔 변수의 상대적 개념인 상수에 대해서 알아보자.
상수는 크게 이름이 있는 상수와 이름이 없는 상수로 나뉜다.
상수란, 그 이름이 의미하듯이 변경이 불가능한 데이터를 뜻한다.
int num = 30 + 40;
여기서 이 한 줄의 코드가 진행될 때의 단계를 살펴보자
1. 정수 30과 40이 메모리 공간에 상수의 형태로 저장된다.
2. 두 상수를 기반으로 덧셈이 진행된다.
3. 덧셈의 결과로 얻어진 정수 70이 변수 num에 저장된다.
이 과정에서 상수(30과 40)는 메모리 공간에 할당 될 때 이름이 없다.
이렇듯 이름이 없는 상수를 가리켜 '리터럴(literal) 상수' 또는 그냥 '리터럴'이라 한다.
자료형은 변수만을 위해서 존재하는 것이 아니다.
자료형은 상수를 위해서도 존재한다.
메모리상에 저장되는 모든 데이터는 자료형이 결정되어야 한다.
그래야 저장하는 방법이 정해지기 때문이다.
그럼 이와 관련해서 다음 코드를 보자.
int main(void)
{
int inum = 5; // 정수형 상수
double dnum = 7.15; // 실수형 상수
...
}
위 코드의 세 번째 행에 등장하는 상수 5와 같이 int형으로 표현 가능한 정수형 상수는 int형으로 메모리 공간에 저장하기로 약속되어 있다.
물론 int로 표현이 불가능한 크기의 정수는 그보다 큰 자료형의 정수로 표현이 된다.
그리고 네 번째 행에 등장하는 상수 7.15와 같이 double형으로 표현 가능한 실수형 상수는 double형으로 저장하기로 약속되어 있다.
정수형에는 int만 있는게 아니고, 실수형에는 double만 있는 게 아니다.
그렇다면 그 이외의 자료형을 기반으로 상수를 표현하려면 어떻게 해야 할까?
해당 자료형을 의미하는 접미사를 붙여주면 된다.
int main(void)
{
float num1 = 5.789; // 경고 메세지 발생
float num2 = 5.789f; // 경고 메세지 발생 안 함
...
}
이름이 있는 상수는 '심볼릭(Symbolic) 상수'라 한다.
이러한 심볼릭 상수를 표현하는 방법에는 두 가지가 있는데, 그 중 하나는 'const 키워드'를 사용하는 방법이고, 또 하나는 매크로를 이용하는 방법이다.
"상수를 표현할 때에는 선언과 동시에 초기화를 해야 한다."
const int MAX=100;
const int MIN;
MIN=1; //컴파일 에러 발생
MAX는 100으로 초기화 되어 더 이상 값을 변경할 수 없다.
MIN도 상수지만 쓰레기 값으로 초기화 되고, MIN=1;은 컴파일 되지도 않는다.
상수의 이름은 모두 대문자로 표시하고, 둘 이상의 단어로 연결할 때는 _(언더바)를 이용해서 두 단어를 구분하는 것이 관례이다.
7. 대입연산의 과정에서 발생하는 자동 형 변환
double num = 100;
이 경우 num은 double형이지만 100은 int형이다.
따라서 int형 정수인 100이 100.0으로 형 변환되어서 num에 저장된다.
int num = 3.14;
이 경우에는 double형 상수 3.14가 int형으로 형 변환되어서 변수 num에 저장된다.
그런데 int형으로는 소수점 이하의 값을 표현할 수 없으므로, 형 변환의 과정에서 3.14가 3으로 변환되어서, 그 결과값이 num에 저장된다.
이렇듯 실수형 데이터를 정수형 데이터로 변환하는 과정에서 '소수부분의 손실'이 발생한다.
int num = 129;
char ch = num;
129가 저장된 변수 num의 비트 열은 다음과 같다.

그런데 이 데이터를 변수 ch에 저장하기 위해서는 1바이트 크기로 줄여야 한다.
따라서 이 경우에는 '상위 바이트의 손실'이 발생하며 그 결과는 다음과 같다.

int형 변수 num(4바이트)이 char형 변수 ch(1바이트)에 저장되면서 맨 왼쪽의 1이 MSB가 되버렸다.
이는 정수로 -127이다.
8. 정수의 승격에 의한 자동 형 변환
앞서 잠깐 나왔던 int보다 작은 크기의 정수형 데이터는 int형 데이터로 형 변환이 되어서 연산이 진행된다고 하였는데, 이러한 형태의 형 변환을 가리켜 '정수의 승격(Integral Promotion)'이라 한다.
9. 피연산자의 자료형 불일치로 발생하는 자동 형 변환
double num = 3.14 + 10;
이 문장에서 실수형 데이터 3.14와 정수형 데이터 10의 합을 요구하고 있다.
그런데 정수와 실수는 사실상 덧셈이 불가능하다.
CPU는 같은 자료형의 두 피연산자를 대상으로만 연산이 가능하도록 설계되어 있기 때문이다.
따라서 위 문장이 요구하는 덧셈 연산을 진행하기 위해서는 둘 중 하나를 형 변환해야 하는데 어떤 것이 좋은 선택일까?
3.14를 정수로 형 변환해버리면 0.14부분이 사라져 버리기 때문에, 당연히 10을 실수형 데이터로 형 변환하는게 좋은 선택일 것이다.
이렇듯 피연산자의 자료형이 일치하지 않아서 발생하는 자동 형 변환은 데이터의 손실을 최소화하는 방향으로 진행된다.
10. 명시적 형 변환: 강제로 일으키는 형 변환
int num1=3, num2 =4;
double result=0;
result = num1/num2;
printf("%f", result); //0.000000 출력
연산결과의 자료형은 피연산자의 자료형과 일치하기 때문에, 3을 4로 나누면 0.75지만, 나눗셈의 결과는 0이 되고(정수형 나눗셈 결과의 몫), 이 값이 double형으로 자동 형 변환되어 변수 result에 저장된다.
따라서 0.00000 이 출력된다.
그럼 원하는 결과(0.75)가 출력되려면 어떻게 해야할까?
result = (double)num1/num2;
(double)은 변수 num1에 저장된 값을 double형으로 변환하라는 뜻이다.
이렇게 사용되는 소괄호를 가리켜 '형 변환 연산자(type casting operator)'라 한다.
이렇게 형 변환이 일어난 경우 다음과 같이 진행된다.
result = 3.0/num2;
이어서 나눗셈을 진행할 차례인데, 산술연산의 형 변환 규칙에 의해 num2에 저장된 값도 double형으로 자동 형 변환된다.
result = 3.0/4.0;
그리고 마지막으로 나눗셈이 진행되고, 그 결과가 result에 저장된다.