독서/C Programming : A Modern Approach

[KNK] chapter 7 : 기본형

도리언 옐로우 2024. 8. 31. 22:47

1. 여러가지 기본형

(1) 정수형

파이썬에서는 일반적인 정수부터 매우 큰 정수까지 모두 int로 퉁치는 반면, C언어에서는 다양한 정수 자료형을 제공하고 있다. 우선 정수형은 부호가 존재하는 signed 정수형과 존재하지 않는 unsigned 정수형으로 구분되는데, C에서 정수형은 기본적으로 signed 정수형을 의미한다. 여기서 부호는 맨 처음의 비트값으로 정해지는데, 해당 비트값이 0이라면 0 이상의 수를 나타내고, 1인 경우에 음수를 나타내게 된다. 따라서 16비트 정수의 최대값은 0111111111111111, 즉 $2^{15}-1$에 해당하는 32767이 된다. 이와달리 unsigned의 경우에는 최대값은 1111111111111111에 해당하는 65535가 될 것이다.

 

C의 정수형은 기본적으로 6가지라고 보면된다. 다음 표에는 64비트 환경에서의 일반적인 정수형 값의 범위를 적어놓았다. 정수간의 산술 연산의 결과가 해당 정수 값의 범위를 벗어나는 경우 등에서 overflow가 발생하게 된다. 

정수형 최소값 최대값
short int - %hd -32768 32767
unsigned short int - %hu 0 65535
int - %d -2147483648 2147483647
unsigned int - %u 0 4294967295
long int - %ld -9223372036854775808 9223372036854775807
unsigned long int - %lld 0 18446744073709551615

다만 이는 모든 컴파일러 혹은 컴퓨터에서 적용되는 범위가 아님에 유의해야 한다. 각 환경에서의 정확한 범위는 표준 라이브러리인 <limits.h>를 이용하면 알 수 있다고 한다.

 

더욱 큰 정수의 연산이 필요해짐에 따라 C99에서는 long long int와 unsigned long long int의 두가지 정수형이 추가되었다. 이 long long 자료형은 최소 64비트이고 이를 사용할 경우 컴파일러가 128비트의 정수형을 지원해 줄 수 도 있다고 한다.

(2) 소수형

C언어에는 소수점 형식에 따라 세 가지 소수형이 존재한다. 각 소수형은 정밀도에서 차이가 있는데, 다만 컴퓨터마다 소수점을 저장하는 방식이 다르기 때문에 C표준에서는 각 자료형이 제공하는 정밀성의 정도에 대해 정의하지 않는다고 한다. 이하에는 IEEE 표준을 기준으로 소수점을 구현한 경우의 정밀도를 함께 표시하였다. 즉 IEEE표준을 준수하지 않는 컴퓨터에는 적용되지 않는 내용이다.

  • float - %f: 단일 정밀도 소수점 (소수점 이하 6자리의 정밀도)
  • double - %lf: 2배 정밀도 소수점 (소수점 이하 15자리의 정밀도)
  • long double - %Lf : 확정 정밀도 소수점 (해당 자료형의 길이는 기계마다 다름)

(3) 문자형

모두가 아는바와 같이 char형 변수에 문자 하나를 할당할 수 있다. 약간 신기한 점은 C언어에서 문자는 정수와 밀접한 관계가 있다는 것이다. C언어에서 문자는 값이 작은 정수와 같고, 문자형 상수들은 사실 내부적으로는 char형이 아닌 int형으로 취급한다고 한다. 따라서 계산할때 문자가 있으면 그냥 정수 취급이 되는데, 예를들어 printf("%d", 'a' + 1);를 실행하면 98이 출력된다.

 

문자를 정수처럼 다룰 수 있기 때문에, char형에서는 int처럼 부호 여부를 정해줄 수 있다. signed의 경우라면 일반적으로 그 값의 범위는 -128에서 127사이의 값이 될 것이다. C 표준은 일반적인 char형이 signed 인지 unsigned인지 지시해주지 않는다고 한다. 물론 char형의 부호에는 별로 관심을 가질 필요가 없기 때문에 크게 문제가 되진 않는데, 다만 필요하다면 char 대신 signed char 또는 unsigned char를 사용하라고 교재에서 팁을 주고 있다.

 

C언어에서 문자 하나의 입출력을 하는 경우 printf와 scanf 대신 putchar와 getchar를 사용할 수 있다. 이 둘은 좀더 문자형에 맞게 최적화가 되어 있는 느낌이어서 printf와 scanf에 비해 속도가 빠르다고 한다. getchar의 경우 호출할때마다 문자 하나를 읽고 해당 문자를 반환하는 함수인데, 좀더 정확히는 int 값을 반환한다. (그 이유는 추후에 다시 설명해준다고 한다.)

 

2. 형변환 및 형정의

(1) 형변환

형변환은 사실 지금까지 무의식적으로 많이 써온 개념이다. 프로그래밍으로 정수 3과 소수 2.4를 더하면 자연스럽게 5.4를 출력해 주었다. 잘 생각해보면 int형과 float형을 더해서 float형을 결과값으로 내보낸 것이다. 그런데 컴퓨터 입장에서는 16비트 정수를 더하는 것처럼 같은 형 사이의 연산은 매우 간단하지만 16비트 정수와 32비트 정수 또는 32비트 소수를 더하는 것은 전혀 다른 문제가 된다.

 

C언어의 형변환은 컴파일러가 자동으로 수행하는 암시적 형변환과 프로그래머가 직접 변환해주는 명시적 형변환이 있다. 위 예시는 암시적 형변환의 예시인데, 이 암시적 변환을 수행하는 규칙은 상당히 복잡하다고 한다. int형이나 float형 등 다른 자료형은 값을 완전히 다른 방식으로 저장하기 때문이다.

 

암시적 형변환의 기본적인 전략은 피연산자를 두 형태의 값을 모두 포함할 수 있는 가장 작은 형으로 변환하는 것이다. 아래의 예시를 보면 쉽게 이해할 수 있다.

// 산술변환

char c;
short int s;
int i;
unsigned int u;
long int l;
unsigned long int ul;
float f;
double d;
long double ld;

i = i + c;      /* c는 정수로 변환됨                 */
i = i + s;      /* s는 정수로 변환됨                 */
u = u + i;      /* i는 무부호 정수로 변환됨          */
l = l + u;      /* u는 장정수로 변환됨               */
ul = ul + l;    /* l은 무부호 장정수로 변환됨         */
f = f + ul;     /* ul은 소수형으로 변환됨            */
d = d + f;      /* f는 2배소수형으로 변환됨          */
ld = ld + d;    /* d는 장2배소수형으로 변환됨        */


// 할당중 변환 : 더 넓은 변수에 할당하는 경우

char c;
int i;
float f;
double d;

i = c;    /* c는 정수형으로 변환됨    */
f = i;    /* i는 소수형으로 변환됨    */
d = f;    /* f는 2배소수형으로 변환됨 */


// 할당중 변환 : 더 좁은 변수에 할당하는 경우

int i;
i = 842.97;     /* i의 값은 842다 */
i = -842.97;    /* i의 값은 -842다 */

c = 10000;      /*** 잘못됨!! ***/
i = 1.0e20;     /*** 잘못됨!! ***/
f = 1.0e100;    /*** 잘못됨!! ***/

 

형변환에서는 하나 주의해야할 함정이 있는데, 바로 signed 피연산자와 unsigned 피연산자가 섞여있는 경우이다. 이 경우 피연산자가 unsigned 값으로 변환시키는데, 이 과정에서 파생되는 오류는 파악하기가 매우 어렵다고 한다. 예를 들어 int형 변수 x = -10과 unsigned int형 변수 y = 100이 있을 때 x < y 는 참으로 1을 반환할 것이라고 생각할 수 있다. 그러나 비교연산 이전에 x가 unsigned int형으로 변환되면서 4294967286으로 변환되고, 이후 비교연산을 통해 x < y는 거짓으로 0을 반환하게 된다.

 

명시적 형변환의 경우 이름 그대로 명시적이어서 이해하기 쉽다. 형변환하려는 변수 앞에 변환하고자 하는 타입을 괄호와 함께 명시해주면 된다. 예컨대 i = (int) f; 와 같은 식이다.

(2) 형정의

형정의는 특정 자료형에 대해 새로운 이름을 부여하는 방식을 말한다. typedef 키워드를 사용하여 정의하게 된다.

typedef unsigned int uint;

위 예시처럼 unsigned int를 uint라는 새로운 이름을 부여하여 사용할 수 있다.

 

형정의는 코드의 가독성을 높여 줄 수 있고, 유지보수성을 높이는데 활용될 수 도 있다. 여기서 유지보수성을 높인다는 것이 바로 와닿지 않을 수 있는데, 예를들어 typedef을 통해 어떤 자료형에 이름을 붙인 상황에서, 나중에 보니 다른 자료형을 사용했어야 하는 경우가 있을 수 있다. 이러한 경우 코드의 모든 부분에서 일일이 찾아 고쳐야 하겠지만, typedef을 사용해서 형정의를 했다면 해당 코드 한줄의 변경만으로 이 작업을 완료 할  수 있게된다. 특히 컴퓨터별로 자료의 범위가 다를 수 있다는 점에서, 형정의는 호환성이 중요한 프로그램을 작성할 경우에 그 의의가 더욱 커진다. 

3. 기타 알게된 것 들

  • C에서는 정수형 상수가 10진수, 8진수, 16진수 세 가지 방법으로 작성될 수 있다. 10진수는 0으로 시작하지 않고, 8진수는 반드시 0으로 시작해야 하며, 16진수는 반드시 0x로 시작해야 한다. 그리고 이들을 섞어서 사용할 수 도 있다.
  • 16진수는 0에서 9까지의 숫자와 a에서 f까지의 알파벳을 사용하는데, 이 문자들은 대소문자 구분을 하지 않아도 된다.
  • overflow와 관련하여, signed 정수간 연산의 경우에는 프로그램이 예상치 못한 결과를 낼 것이나, unsigned의 경우에는 프로그램의 행동이 정의되어 있어 mod 값이 결과가 된다고 한다.
  • double의 %lf는 printf가 아닌 scanf의 서식 문자열에서만 이렇게 작성한다. 즉 printf에서 f 등 변환은 float과 double 둘 다 혼용해서 사용할 수 있게 해준다.
  • sizeof 연산자는 특정 형의 값을 저장하는데 필요한 메모리가 얼마인지 알려준다. 해당 연산자는 상수, 변수 및 표현식에 사용할 수 있다.