자료형에 대해 공부하기 전에 간단히 배경지식을 훑어보겠다.
컴퓨터는 2진수를 사용한다. 두 개의 기호(0, 1)로 데이터를 표현하는 방식이다.
비트는 컴퓨터가 표현하는 데이터의 최소단위로, 2진수 값 하나를 저장할 수 있는 메모리의 크기이다. 바이트는 8비트를 말한다.
C언어는 10진수 외에 8진수, 16진수로 데이터를 표현할 수 있다. 0x로 시작하면 16진수, 0으로 시작하면 8진수이다. 컴퓨터는 값이 어떤 방식으로 표현되던지 2진수로 저장하므로, 표현에 상관없이 값이 저장된다.
16진수 한자리값은 4비트이며 8진수 한자리 값는 3비트이다.
컴퓨터가 정수와 실수를 표현하는 방식에 대해서는 이미 알고 있다. 이 때, 컴퓨터가 실수를 표현하는 방식에 오차가 존재한다.
실수는 다음과 같이 표현하기 때문에, 정확히 0.0이 될 수 없다. 이와 같이 컴퓨터는 실수의 정확한 값이 아닌 근사치로 실수를 표현하게 되고, 이 때 생기는 오차를 부동소수점 오차라 한다. 간단한 예제를 통해 부동소수점 오차를 시각적으로 확인하도록 하겠다.
#include <stdio.h>
int main(void) {
int i = 0;
float result = 0.0;
for (; i < 100; i++)
result += 0.1;
printf("%f", result);
return 0;
}
10.000002
0.1을 100번 더한 결과, 10.0이 아닌 10.000002이라는 값을 얻었다. 이를 통해 실수의 표현에 오차가 존재한다는 사실을 알 수 있다.
& : 비트 단위로 and 연산
| : 비트 단위로 or 연산
^ : 비트 단위로 xor 연산(배타적 논리합)
~ : 피연산자의 모든 비트 반전
<< : 피연산자의 비트 열을 왼쪽으로 이동(shift 연산)
>> : 피연산자의 비트 열을 오른쪽으로 이동(shift 연산)
👉🏻왼쪽 시프트 연산 <<을 할 경우 한 비트 당 *2의 결과가 나오고, 오른쪽 시프트 연산 >>을 할 경우 /2의 결과가 나온다.
a <<= b : a = a << b
a >>= b : a = a >> b
a &= b : a = a & b
a ^= b : a = a ^ b
a |= b : a = a | b
자료형은 데이터를 표현하는 방법이다. C언어에서 제공하는 기본 자료형에 대해 알아보겠다.
변수를 선언하여 데이터를 저장할 공간을 만드려면, 어떤 데이터를 저장할 것인지 알려줘야 한다. 기본 자료형에는 정수형과 실수형이 있다. 정수형과 실수형에도 크기에 따라 여러 종류의 자료형이 있다.
C언어에서 사용하는 대표적인 자료형은 정수형 int, 실수형 double이며 문자열에는 char를 사용한다.
정수의 연산을 할 때 의도적으로 형 변환을 하지 않는 경우 int로 변환되어 연산이 이루어지며, 실수의 연산의 경우 double로 변환되어 연산이 이루어진다.
👉🏻연산자 sizeof를 사용하면 자료형의 크기를 알 수 있다. (❗sizeof는 함수처럼 보일 수 있지만 함수가 아니라 연산자이다.)
이 연산자의 피연산자로는 변수, 상수, 자료형이 될 수 있다.
unsigned는 정수 자료형에만 해당하는 내용인데, 0아래, 즉 음수를 표기하지 않고 양수만을 표기하는 방식이다. 부호 비트가 필요하지 않게 되므로 한 비트 더 수의 크기를 위해 사용되고, 이는 표현할 수 있는 수의 범위를 2배 늘리는 결과를 불러온다. 자료형 이름 앞에 unsigned 선언을 표기하면 된다.
숫자로 문자를 표현하기 위해 숫자를 문자에 연결한 것이다. 미국 표준 협회 ANSI(American National Standards Institute)에서 제정한 ASCII(American Standard Code for Information Interchange) 코드를 사용하는데, 7bits로 표현하며 알파벳과 특수문자를 포함해 총 128개 문자로 이루어져있다.
프로그램 상에서 문자가 어떻게 표현이 되는지 간단히 살펴보자.
아래 두 코드의 출력 결과를 살펴보자.
#include <stdio.h>
int main(void) {
char ch1 = 'A';
char ch2 = 'B';
printf("%c\n", ch1);
printf("%c", ch2);
return 0;
}
A
B
#include <stdio.h>
int main(void) {
char ch1 = 65;
char ch2 = 66;
printf("%c\n", ch1);
printf("%c", ch2);
return 0;
}
A
B
두 코드는 동일한 결과를 가진다.
프로그램 상에서 문자를 표현할 때 작은 따옴표를 사용하여 나타낸다. 이 때 따옴표 안의 문자는 컴파일러에 의해 숫자(아스키 코드 값)로 변환되므로 'A'와 65는 동일하다는 것을 알 수 있다.
서식문자 %c는 문자의 형태로 데이터를 입/출력하라는 의미이다.
상수는 이름이 있는 상수와 이름이 없는 상수로 나눌 수 있다.
리터럴 상수는 변경할 수 없는, 그 값 자체를 의미한다. 리터럴 상수 또한 변수와 마찬가지로 자료형을 근거로 표현이 된다.
int main(void){
int num1 = 10;
double num2 = 30.0;
}
위의 코드에서 num1, num2는 변수, 10, 30.0은 상수이다.
❗R-value와 L-value
R-value와 L-value는 각각 Right value와 Left value를 의미하는데, 이는 두 값이 보통의 대입연산에서 등호 =를 기준으로 오른쪽, 왼쪽에 위치하기 때문이다. C언어에서 변수는 L-value에 해당하며, 리터럴 상수는 R-value에 해당한다고 생각하면 되겠다.
👉🏻변수의 자료형은 우리가 변수를 생성(선언)할 때, 직접 지정해주고 있다. 그렇다면 상수의 자료형은 어떻게 결정될까?
int형 범위 내에서 표현이 가능한 정수는 int형으로, double형 범위 내에서 표현 가능한 실수는 double형으로 저장되도록 되어있다.
아래의 예시를 보자.
#include <stdio.h>
int main(void) {
printf("literal int size: %d\n", sizeof(5));
printf("literal double size: %d\n", sizeof(3.14));
printf("literal char size: %d", sizeof('A'));
return 0;
}
literal int size: 4
literal double size: 8
literal char size: 4
위에서 방금 공부한 바에 따르면 문자 'A'는 1바이트인 char타입으로 저장되어야 할 것 같다. 하지만 문자 'A'는 컴파일러에 의해 정수 65로 변환되므로, 정수를 저장할 때 사용되는 자료형인 int로 저장되었기 때문에 4바이트로 표현되고 있다.
그렇다면 int나 double 외의 자료형으로 상수를 표현하려면 어떻게 해야할까? 접미사를 통해 명시적으로 표기해준다.
int main(void){
float num = 3.14;
return 0;
}
이 코드는 문제없이 작동하지만 경고 메세지가 뜬다.
이 코드의 l-value는 float 타입이지만 r-value는 실수인 상수이므로 double 타입이다. 이 때 접미사를 붙여주면 된다.
int main(void){
float num = 3.14f; //f는 대소문자 모두 가능
return 0;
}
이 때 3.14는 float 타입의 상수가 된다.
정수의 경우, 접미사 l은 long 타입, ll은 long long을 의미한다.
심볼릭 상수는 변수처럼 이름을 가진다. 하지만 상수이므로 값을 변경할 수는 없다. 심볼릭 상수에는 두 가지가 있는데, 하나는 const 상수, 다른 하나는 매크로 상수이다.
const 상수는 변수처럼 선언하되 앞에 const 키워드를 붙이면 된다. 선언과 동시에 초기화를 해야만 한다. 상수의 이름은 모두 대문자로 표기하는 관례를 따른다.
int main(void){
const int STUDENT = 500;
const int TEACHER = 30;
const int CLASS = 20;
}
👉🏻상수에 이름을 붙이는 이유?
📍이름을 통해 해당 상수가 의미하는 바를 알 수 있다.
📍상수를 특정 숫자로 설정했으나 수정하고 싶을 때, 코드 여러 부분을 고칠 필요 없이 초기화 부분만 바꾸면 된다.
위의 예시를 보자. 코드 작성 시 상수 500을 쓰는 대신 STUDENT를 사용한다면, 500이 학생의 수라는 것을 쉽게 알 수 있다. 또, 학생의 수를 이용해 여러 연산을 한 경우, 학생 수를 바꾸고 싶을 때 모든 부분을 고치지 않고 const int STUDENT = 499;와 같이 선언부만 고치면 된다.
데이터 타입을 변환하는 데에는 두 가지 방식이 있다.
📌대입연산에서의 자동 형 변환
int num = 3.14
위의 경우 상수 3.14는 double형이므로 int형으로 변환되어 num에 저장된다. 이 때 소수점 이하의 값은 버려져 3으로 저장된다.
double num = 5;
위의 경우에 5는 int형이므로 double로 변환되어 num에 저장된다. 이 때 5는 5.0으로 저장된다.
📌정수의 승격(Integral Promotion)
CPU가 연산할 때 처리 효율을 위해 int보다 크기가 작은 정수형 데이터의 경우 int로 형 변환을 하여 연산한다.
📌연산 시 피연산자의 자료형이 불일치하는 경우
연산을 할 때 피연산자들의 자료형이 다른 경우 자동 형 변환이 일어나 자료형을 통일한다.
double num = 3.14 + 10;
위의 문장에서 피연산자인 상수들은 각각 double과 int이다. 피연산자의 자료형이 다르므로 연산할 수 없다. 따라서 이 때 형 변환이 일어나는데, double을 int로 변환한다면 소수점 이하는 버려지므로 int형 상수 10을 double 10.0으로 변환하여 연산이 이루어진다.
명시적 형 변환은 연산자를 이용해 원하는 자료형으로 의도적으로 변환하는 것이다.
아래 코드를 보자.
#include <stdio.h>
int main(void) {
double result;
result = 10 / 3;
printf("%f", result);
return 0;
}
3.000000
우리는 10을 3으로 나눴고, 그 결과를 double형 변수 result에 저장해 실수 형태로 출력하였다. 그렇다면 3.333333이 출력되지 않은 이유는 뭘까?
result = 10 / 3;
이 문장에서 피연산자는 상수 10과 상수 3이므로 둘다 int형이고, 계산이 이루어질 때도 int형으로 이루어져 결과 역시 int형이 된다. 정수끼리의 나눗셈 연산은 몫만을 결과로 취하고, 3이 double형으로 자동 형 변환되어 3.000000이라는 결과를 낳은 것이다.
이 때 명시적 형 변환을 해주면 원하는 결과를 얻을 수 있다.
#include <stdio.h>
int main(void) {
double result;
result = (double)10 / (double)3; //result = 10 / (double)3; 혹은 result = (double)10 / 3;로 하여도 동일한 결과
printf("%f", result);
return 0;
}
3.333333
괄호 안에 원하는 자료형을 넣으면 자료형이 변환된다.
❗명시적 형 변환은 일시적으로 그 문장에 적용되는 것이다.
int num = 3;
double dnum = (double)num;
위의 경우 num을 double로 형 변환 한 것은 해당 문장에 적용되는 것이지, 형 변환 이후 num이 double형이 되는 것은 아니다.
메모리 공간 할당 시에 필요한 것은
📍변수 이름
📍변수의 자료형
📍변수의 크기
이미지 출처 : http://www.c-jump.com/bcc/common/Talk2/Cxx/IEEE_754_fp_standard/IEEE_754_fp_standard.html
https://techbeamers.com/c-datatypes/