본문 바로가기

Programming/C Programming Modern Approach (K.N.K)

[KNK 정리] 7장: Basic Types

반응형

 

글과 관련없는 사진

요약/정리

integer types

integer type은 말 그대로 정수를 데이터로 하는 변수의 타입이다. signed와 unsigned로 나누는데, signed의 경우 첫 번째 비트(MSB, Most Significant Bit)를 부호 비트로 사용한다. MSB가 1이면 음수, MSB가 0이면 양수이다. unsigned의 경우는 항상 0보다 크거나 같은 정수만 표현할 수 있으며, MSB를 부호  비트로 사용하지 않는다.

 

기본적으로 C에서 integer type은 별도로 명시하지 않는 한 signed이다. 가장 일반적인 integer type은 앞의 글에서도 많이 사용했단 int이다. 그 외에 필요에 따라 long int, short int도 사용할 수 있다. long int, short int는 long, short로 줄여서 쓸 수 있다. 참고로 unsigned short int와 short unsigned int, int short unsigned는 같은 의미이다. (type specifier의 순서는 중요하지 않다.)

 

integer type의 크기는 컴파일러와 아키텍처마다 다르다. 표준에 나와있는 내용은 아래 정도이다. limits.h에서 각 타입의 최댓값, 최솟값을 확인할 수 있다.

 

char는 1바이트로 고정

short int는 최소 2바이트

long int는 최소 4바이트

long long int는 최소 8바이트 (C99에서 추가됨)

sizeof(short int) <= sizeof(int) <= sizeof(long int)

integer constants

C에서는 10진수, 8진수, 16진수로 integer constant를 사용할 수 있다. 1, 10과 같은 일반적인 숫자는 10진수, 012, 017처럼 0으로 시작하는 숫자는 8진수, 0xFF, 0x10처럼 0x로 시작하는 숫자는 16진수 상수이다. integer constant의 type은 C89에선 상수의 크기에 따라 int -> long int -> unsigned long int 순서대로 커진다. 또한, 상수 뒤에 U or u를 붙이면 unsigned라는 의미를, Lor l을 붙이면 타입이 long int라는 의미를 갖는다. 둘이 혼용할 수도 있다. (UL, LU 등등)

 

C99에서는 long long int도 추가되었으므로 ll or LL도 사용할 수 있다. 또한, suffix (u, l, ll 등)가 붙지 않은 integer constant의 타입은 int -> long int -> long long int 순서대로 정해지며, 8진수/16진수 상수에 대해서는 int -> unsigned int -> long int -> unsigned long int -> long long int -> unsigned long long int 순으로 정해진다.

 

integer overflow

어떤 type이 표현할 수 있는 최댓값보다 더 큰 수 or 작은 수가 계산되는 것을 overflow라고 한다. C에서는 signed integer type의 연산에 대한 overflow는 undefined이지만, unsigned integer type에 대한 연산의 결과는 (연산의 결과) % 2^n이 된다.

extended integer types

표준상 정의되어있지는 않지만, 표준에서 명시하지 않은 extended integer type도 사용할 수 있다. gcc의 __int128이 그 예이다. 

 

floating types

floating types는 수의 정수부 뿐만 아니라 소수부 까지를 표현할 때 사용된다.

float: single-precision floating-point

double: double-precision floating-point

long double: extended precision floating-point

 

C 표준에서는 float, double, long double의 범위와 정밀도, 표현 방식에 대해서 딱히 명시하는 바가 없다. 다만 대부분의 컴파일러는 IEEE 754에 따라 이를 구현하고 있다.

 

floating constants

C에서 기본적으로 floating constant의 type은 double이다. suffix로 f, F (float)나 l, L (long double)을 붙여서 type을 명시할 수 있다.

character types

character type (char)에는 'a', '0', ' '와 같은 문자를 저장한다. char의 저장된 값은 character set에 따라서 다르게 해석될 수 있다. ASCII가 현재로서 가장 인기있는 character set이라고 할 수 있다. 주의할 점은, 컴퓨터에 따라서 character set이 다를 수도 있다는 점이다.  ASCII에 의존적인 코드는 다른 컴퓨터에서 호환되지 않을 가능성이 존재한다. (물론 대부분의 경우는 ASCII를 쓴다.) 아래 사진을 보면 유추할 수 있듯, character type도 사실은 정수를 저장한다. 다만 저장되는 정수가 문자를 의미할 뿐이다.

 ascii code table from 위키백과

signed and unsigned characters

C에서는 character type이 signed인지 unsigned인지 명시하고 있지 않다.  따라서 signed 또는 unsigned라고 가정하는 것은 좋지 못하다.

escape sequences

모든 문자를 코드상에서 쓸 수 있으면 좋겠지만 엔터, 백스페이스와 같이 non-printable character는 나타내기가 힘들다. 따라서 C는 8진수 또는 16진수로 특정 문자를 표현할 수 있다.

 

8진수 : \33, \10 등 \ 뒤에 8진수 숫자들 or \033, \010 처럼 숫자가 0으로 시작할 수도 있다.

16진수: \x1B, \xff 등 \ 뒤에 16진수 숫자들

 

https://www.geeksforgeeks.org/escape-sequences-c/

그리고 몇몇 문자에 관해서는 위의 사진처럼 escape 문자가 별도로 존재한다.

 

type conversion

사람과 달리 컴퓨터는 두 operand가 같은 타입일 때만 연산을 할 수 있다. 3.14 (double) + 1 (int)를 계산하려면 float을 int로 변환하든, int를 float을 변환하든 타입을 맞춰주어야 한다는 이야기다. 따라서 어떤 복잡한 expression (3.14 + 1)을 작성했을 때, subexpression (예시에선 3.14와 1)의 타입을 적절하게 변환해줄 필요가 있다.

implicit conversion vs explicit conversion

type conversion은 implicit conversion (묵시적 변환)과 explicit conversion (명시적 변환)으로 나뉜다. explicit conversion은 cast 연산자로 직접 타입을 명시해주는 것이고, implicit은 컴파일러가 스스로 변환해주는 것이다.

 

implicit conversion이 일어나는 경우는 다음과 같다.

- arithmetic, logical expression의 operand의 타입이 서로 다를 때 (= usual arithmetic conversions)

- 대입 연산에서 rvalue의 타입이 변수의 타입과 다를 때

- 함수 호출시 인자의 타입이 파라미터의 타입과 다를 때 

- return문에서 반환하는 expression의 타입이 함수의 return type과 다를 때

이번 장에서는 앞의 두 가지 경우만 다룬다.

usual arithmetic conversions

usual arithmetic conversions에서 중요한 것은, "데이터의 손실이 일어나지 않는 방향으로 변환하는 것"이다. 가장 작은 타입 (narrowest type)을 적당한 자료형으로 변환해주어야 한다.

 

주의: 서로 다른 부호를 가진 operand로 expression을 만드는 건 좋지 않다. 부호를 정하는 규칙이 까다롭기 때문에 실수를 하기가 쉽다.

C89에서의 usual arithmetic conversions

C89에서 usual arithmetic conversion은 다음과 같이 진행된다.

 

operand 둘 중 하나가 floating type인 경우

- 둘중 한 operand가 long double인 경우, 나머지 하나를 long double로 변환한다.

- 둘중 한 operand가 double인 경우, 나머지 하나를 double로 변환한다.

- 둘중 한 operand가 float인 경우, 나머지 하나를 float로 변환한다.

 

operand 둘 모두 floating type이 아닌 경우

- 먼저 양쪽 operand에 integeral promotion을 한다. (short int, char을 int로 변환한다.)

- 그 다음 두 operand 중 더 작은 타입(narrower type)을 int -> unsigned int -> long int -> unsigned long int 순서대로 가장 적절한 타입으로 변환한다. 단, 두 operand의 타입이 unsigned int와 long int이며, unsigned int와 long int의 크기가 같은 경우에는 모두 unsigned long int로 변환한다.

C99에서의 usual arithmetic conversions

C99에서는 integer conversion rank라는 개념이 생긴다. rank가 높은 순서대로 나열해보자.

 

1. long long int, unsigned long long int

2. long int, unsigned long int

3. int, unsigned int

4. short int, unsigned int

5. char, signed char, unsigned char

6. _Bool

 

C99에서 usual arithmetic conversion은 다음과 같이 진행된다.

operand 둘 중 하나가 floating type인 경우 (C89와 동일하다)

- 둘중 한 operand가 long double인 경우, 나머지 하나를 long double로 변환한다.

- 둘중 한 operand가 double인 경우, 나머지 하나를 double로 변환한다.

- 둘중 한 operand가 float인 경우, 나머지 하나를 float로 변환한다.

 

operand 둘 모두 floating type이 아닌 경우

- 두 operand에 대해 integer promotion을 먼저 수행한다.

- 두 operand 모두 signed이거나 unsigned라면, rank가 낮은 타입을 높은 타입으로 변환한다.

- unsigned의 랭크가 signed의 랭크보다 높으면 signed의 타입을 unsigned의 타입으로 변환한다.

- signed의 타입이 모든 unsigned 타입의 값을 표현할 수 있으면, unsigned의 타입을 signed의 타입으로 변환한다.

- 그 외에는 두 operand를 'signed 타입의 unsigned 타입'으로 변환한다.

conversion during assignment

대입 연산 시에는, rvalue의 타입이 변수의 타입으로 변환된다.

casting

앞서 말했듯, casting operator는 expression의 타입을 명시적으로 변환할 때 사용된다.

형식: ( type-name ) expression

 

참고: C에서는 casting operator의 우선순위가 높기 때문에 (long) i * i는 (((long) i) * i)가 된다.

type definitions

형식: typedef type-name identifier

typedef float dollar;

이렇게 type definition을 이용하면 dollar라는 타입을 float 대신 사용할 수 있다. type definition을 사용하면 가독성과 이식성이 올라간다. 그리고 수정하기가 편하다.

the sizeof operator

형식: sizeof expression or sizeof ( type-name )

sizeof 연산자는 type-name 또는 expression에 대해 사용할 수 있으며, 연산의 결과는 타입이 size_t이다. 형식을 보면 알 수 있듯 type-name에 대해서는 괄호를 써야하고, expression에 대해서는 괄호를 쓰지 않아도 된다. 다만, sizeof 연산자의 우선순위가 높기 때문에 괄호를 써주는 것이 좋다. 예를들어 sizeof i + j는 sizeof (i) + j와 같기 때문에, sizeof (i + j)라고 써주는 게 좋다.

 

Exercises

 

Programming Projects