피연산자의 자료형
일반적으로 연산을 하기 위해서는 피연산자가 필요하다. 덧셈과 같은 이항 연산자의 경우에는 두 개의 피연산자가 필요하다. C언어에서는 피연산자의 자료형을 제대로 알아야 해당 피연산자 간에 오류없는 연산이 가능하다. 지금부터 피연산자의 종료에 따른 연산 형태를 먼저 알아보겠다.
상수와 상수
상수와 상수 연산은 컴파일되면서 기계어 명령으로 변환되지 않고, 연산 결과에 해당하는 숫자로 변경된다. 간단하게 예를 들어서 확인해보자. 아래와 같은 연산이 있다면 이 연산은 덧셈 명령어로 기계어 번역이 되지 않고 10이라는 숫자로 변환된다.
5 + 5
따라서 상수와 상수 연산은 컴파일러에 의해 연산이 이루어지기 때문에 실제 프로그램 실행 시 연산이 이루어지는 다른 연산들과 차이가 있다. 하지만, 컴파일러에 의해서 이루어진다고 해도 연산은 연산이기 때문에 피연산자로 사용된 상수에도 자료형이 있어야 한다. 변수와 달리 상수의 경우에는 별도의 자료형 선언 없이 바로 사용하기 때문에 컴파일러가 상수 표현마다 자료형을 지정해놓고 사용한다. 그래서 위의 경우에는 정수 형태의 상수이기 때문에 컴파일러는 int 자료형을 사용해 위 상수값을 처리한다.
변수와 상수
C언어에서는 변수를 사용하기 위해서는 변수를 선언하는 작업이 선행되어야 한다. 그래서 변수를 먼저 선언하고 연산을 진행해야 한다.
int data;
data + 1;
컴파일러는 data + 1 연산을 처리할 때에 data 변수의 선언 문장을 참조하여 int 자료형임을 알게 된다. 그리고 1은 정수 형태의 상수이므로 int 자료형을 결정하여 int 자료형의 연산을 수행한다.
변수와 변수
피연산자가 모두 변수인 경우이다. 위의 변수와 상수에서 변수의 경우에는 선행작업으로 변수를 선언하는 작업이 필요하다고 했다. 그러므로 변수와 변수의 경우에는 변수 두 개의 선언 작업이 필요하다. 이렇게 선언된 변수에서 자료형을 알 수 있으므로 명확하게 두 피연산자의 자료형을 알 수 있다.
연산의 결과와 피연산자의 값의 관계
모든 연산에는 결과값이 존재한다. 이 때 결과값이 존재한다 하더라도 대입연산자(=)를 사용하기 전까지는 이 결과값을 사용할 수 없다.
int data = 5;
data + 2;
위의 경우를 예를 들어보면 'data + 2' 연산의 결과값은 7이 된다. 하지만, data의 값이 7이 되는 것이 아니다. 결과값은 연산을 수행하는 CPU 레지스터에 임시로 저장되어 있기 때문에 위와 같이 연산을 수행하면 연산은 진행되지만 결과를 사용할 수 없다. 따라서, 연산의 결과값을 사용하기 위해서는 아래와 같이 대입연산자(=)를 사용해야 한다.
int data = 5;
data = data + 2;
C언어는 'data = data + 2'와 같이 명령문을 사용하더라도 내부적으로는 아래와 같이 CPU 레지스터에 임시저장하고 저장된 값을 다시 대입하는 방식으로 처리된다.
레지스터 = data + 2;
data = 레지스터;
확실한 이해를 위해서 또 다른 예를 들어보겠다.
int data1 = 3, data2 = 4, sum;
sum = data1 + data2 + 1;
위와 같이 하나의 수식에 두 개의 연산자가 사용되는 경우에는 실제로 세 번의 연산이 진행된다. 이유는 연신이 진행되는 과정에서 레지스터가 중간값을 저장하기 떄문이다. 세 번의 연산이 진행되는 과정을 한 번 보겠다.
레지스터 = data1 + data2
레지스터 = 레지스터 + 1
sum = 레지스터
대입 연산자와 자료형
연산의 결과를 저장하기 위해서는 대입연산자(=)를 사용해야 된다고 했다. 지금까지의 예시에서는 연산값과 연산값이 저장되는 변수의 자료형이 일치하여서 정상적으로 연산이 수행되었다. 하지만, 이 두 가지가 다른 경우에는 문제가 발생한다. 한 가지 예를 들어보겠다.
short int data;
data = 5;
위의 연산을 주의깊게 보면 두 피 연산자가 다름을 알 수 있다. data 변수는 short int 자료형을 가지고 있고, 5는 정수형 상수이므로 int 자료형을 가지고 있음을 알 수 있다. 대입연산자를 사용하여 연산을 수행하면 int형의 값을 short int형의 변수에 넣는 것이 되므로 데이터의 손실이 발생할 수 있다. 하지만, 직접 실행을 해보면 오류가 발생하지 않는다. 그 이유는 컴파일러가 5를 int 자료형으로 정의를 했지만, 그 크기가 short int 자료형의 범위에 포함되기 때문이다. 즉, short int 자료형의 범위에 포함되지 않는 값을 대입하려고 하면 오류가 발생한다는 뜻이다. short int 자료형의 범위는 -32,768~32,767이다. 범위에 포함되지 않는 400000의 값을 저장하게 되면 오류가 발생한다. 오류가 나지 않게 형변환 연산자를 사용하여 400000의 값을 short int로 변환해줄 수 있지만, 그렇게 되면 데이터의 손실이 발생하게 되므로 주의해야 한다. 어떻게 손실되는지 한번 알아보겠다.
#include <stdio.h>
int main()
{
short int data;
data = (short int)400000;
printf("1. %d : %08x\n", data, data);
printf("2. %d : %08x\n", 400000, 400000);
return 0;
}
1번은 400000을 short int 자료형인 data 변수에 저장한 후 출력한 것이고, 2번은 400000을 그대로 출력한 것이다. 어떻게 손실되었는지 확인하기 위해서 16진수로 확인을 해보니 데이터 손실을 쉽게 확인할 수가 있다. 위와 같이 임의로 형변환을 직접 하는 것을 명시적 형변환이라 하고, 형변환없이 컴파일러가 내부적으로 형변환을 해주는 것을 암시적 형변환이라 한다.
연산 결과와 자료형
대입연산자를 사용할 때에 변수와 변수, 변수와 상수의 경우가 있음을 확인하였다. 이 경우들은 양쪽의 자료형을 확실하게 알 수 있기 때문에 어렵게 생각하지 않아도 된다. 하지만, 변수와 연산의 결과 값인 경우에는 연산의 결과 값이 어떤 자료형을 가질 것인지 잘 판단해야 한다. 연산의 결과 값은 레지스터에 저장된다고 하였다. 레지스터에 저장되어 있을 때에는 자료형이 없는 상태가 된다. 그렇기 때문에 연산에 참여한 피연산자의 자료형을 참조하여 자료형을 결정하게 된다.
int data = 5;
data = data + 5;
위의 경우에서는 'data + 5' 연산 수식에서 data 변수는 int 자료형이고 5도 정수형 상수이므로 int 자료형을 가진다. 따라서 연산 수식의 결과 값 또한 int 자료형으로 처리된다.
short int data1 = 5, sum;
int data2 = 10;
sum = data1 + data2;
위의 경우는 'data1 + data2' 수식 연산에서 피연산자의 자료형이 다르다. 이런 경우에는 표현 범위가 더 큰 자료형으로 변환 되어 연산이 진행된다. 즉, short int 자료형을 가진 data1 변수가 암시적 형변환으로 int 자료형으로 형변환이 된다. 그런데 sum은 short int 자료형을 가지고 있으므로 또 다시 연산의 결과 값과 자료형이 맞지 않게 된다. 이 때 또 한 번 암시적 형변환이 일어나 연산의 결과 값이 short int 자료형으로 바뀌게 된다. 결론적으로 두 번의 형변환이 발생하게 된 것이다. 이를 코드로 표현해보면 아래와 같다.
sum = (short int)((int)data1 + data2);
위의 코드를 명시적 형변환으로 효율적으로 바꾸면 아래와 같이 사용할 수도 있다.
sum = data1 + (short int)data2;
수식 연산에서 피연산자가 변수와 상수인 경우 변수가 int 자료형일 경우에는 연산이 int 자료형으로 진행되지만, short int 자료형을 가지고 있는 경우에는 범위가 더 큰 int 자료형으로 형변환 되어 연산이 된다. 이를 코드로 표현해보면 아래와 같다.
short int a = 5, sum;
sum = (short int)((int)a + 5);
C언어 컴파일러가 암시적 형변환을 사용하여 맞지 않는 자료형이더라도 연산이 정상적으로 이루어지게 해준다. 하지만, 개발자의 입장에서 자료형이 변경된다는 것은 데이터의 손실을 의미하기 때문에 주의해야 한다. 그렇기 때문에 암시적 형변환에 의존하는 것보다는 명시적 형변환을 사용하는 것을 자주 사용하는 것도 좋다고 생각한다.