정수형 상수를 표기하는 방법은 아주 쉽고 상식적이다. 아라비아 숫자와 부호로 직접 그 값을 표기하면 된다. 다음이 정수형 상수의 예이다.

123

8906299

-86400

이렇게 값을 바로 표기하면 그 크기와 형태를 보고 적당한 타입으로 메모리에 저장된다. 위 예의 경우 차례대로 short, int, int의 타입을 가진다. 123은 2바이트로 표현할 수 있으므로 short형이면 충분하고 8906299는 2바이트의 범위를 넘으므로 int형이 되어야 한다. 만약 크기를 강제로 지정하고 싶으면 상수 뒤에 L(Long, 소문자로 써도 됨)을 붙이고, 부호없는 타입으로 지정하고 싶으면 U(Unsigned, 소문자로 써도 됨)을 붙인다.

예를 들어 28은 short형이지만 28L로 표기하면 4바이트의 long형 상수가 되고 123U는 부호없는 2바이트의 정수인 unsigned short가 된다. 만약 부호없는 4바이트의 상수로 표기하고 싶으면 순서나 대소문자에 상관없이 UL, LU, ul, lu 중 하나를 붙이면 된다. C/C++언어는 타입을 중요하게 생각하므로 상수에도 정확한 타입을 지정할 수 있도록 되어 있다. 정수형 상수를 별도의 표기없이 그냥 쓰면 10진수로 해석된다. 그러나 진법에 따라 다음 두가지 형식으로 상수를 표현할 수도 있다.

8진수 : 0으로 시작하면 8진수로 인식되며 027, 032등과 같이 표현한다. 09같은 상수는 에러로 처리되는데 9라는 숫자는 8진수에서 쓸 수 없는 숫자다.

16진수 : 0x 또는 0X로 시작하면 16진수이다. 0x12ab, 0x3f와 같이 표현한다. 16진수에서 10 이상의 값을 표현하는 A~F는 대소문자에 상관없이 아무 문자나 사용할 수 있다. 접두로 붙는 0x는 알파벳의 "오엑스"가 아니라 숫자 0과 알파벳 x이므로 "영엑스" 또는 "공엑스"로 읽는다.

보편적으로 10진수를 사용하지만 어셈블리와 관계있는 값이나 비트별로 의미가 다른 상수는 16진수로 표기하면 더 편리한 경우가 많다. 아쉽게도 C는 2진 상수 표기법은 제공하지 않으므로 2진수는 16진수로 바꾸어 표기해야 한다. 예를 들어 2진수 10100110은 16진수 0xa6으로 표기한다. 사실 8진 표기보다는 2진 표기법이 더 많이 사용되는데 베이직도 지원하는 2진 표기법이 C문법에 빠진 것은 데니스 리치의 실수가 아닌가 생각된다. 실제로 표준 위원회에 2진 표기법을 도입하자는 건의가 여러 차례 있었으나 익숙해지면 16진수로 암산 가능하다는 이유로 표준에서 제외됐다.

C에서 0으로 시작하는 상수는 8진수라는 것은 잘 알아 두어야 한다. 그렇지 않으면 다음과 같은 엉뚱한 실수를 할 수도 있다. HPNum에 10진수로 17을 대입하고자 했는데 전혀 문제가 없는 문장이기 때문에 컴파일러는 어떠한 에러 메시지도 출력하지 않는다.

 

int HPNum;

HPNum=017;

 

이렇게 하면 상수 017이 8진수로 해석되어 Num에 17이 대입되는 것이 아니라 15가 대입될 것이다. C 컴파일러는 017과 17을 엄연히 다른 상수로 취급하는데 0으로 시작하는 상수는 8진수라는 것을 잘 기억해 두도록 하자.

참고로 진법이란 수치값을 표기하는 다른 방법일 뿐이지 표현하는 값 자체가 다른 것은 아니다. 012나 10이나 0xa나 모두 십진수로 10(이진수로는 1010)을 나타내며 메모리에 기록될 때는 똑같은 값이다. 따라서 "10진수 값을 어떻게 16진수로 바꾸나요?" 이런 질문은 잘못된 것이다. 10진수를 16진수 형태로 출력한다거나 문자열 형태로 저장된 16진수를 10진값으로 구하는 방법을 질문하는 것은 옳지만 말이다.

Posted by 테티스
,

C의 정수형, 더 정확하게 말해서 컴퓨터가 표현하는 정수라는 개념은 수학에서 말하는 정수와는 의미가 약간 다르다. 수학의 정수는 음양으로 무한대의 값을 표현할 수 있지만 유한한 메모리를 가진 컴퓨터는 이런 무한한 값을 표현하지 못하며 자신에게 할당된 메모리 양만큼의 값만 기억할 수 있다. 그래서 가끔 연산 결과가 용량을 넘어서는 경우가 발생하기도 하는데 다음 예제를 실행해 보자.

: overflow

#include <Turboc.h>

 

void main()

{

     short a,b,c;

     unsigned short s,t,u;

 

     a=20000;

     b=30000;

     c=a+b;

     printf("%d+%d=%d\n",a,b,c);

 

     s=20000;

     t=30000;

     u=s-t;

     printf("%d-%d=%d\n",s,t,u);

}


정수형 변수로 간단한 덧셈, 뺄셈을 해 보았는데 실행 결과는 다음과 같다.

20000+30000=-15536

20000-30000=55536

세 개의 2바이트 정수(short) a, b, c를 선언하고 a에 20000, b에 30000을 대입한 후 이 두값을 + 연산자로 더해 c에 대입했다. 그러면 c는 당연히 50000이라는 값을 가져야겠지만 실제 결과는 엉뚱하게도 -15536으로 출력된다. 왜냐하면 a, b, c 변수는 부호있는 2바이트의 정수형인 short형으로 선언되었고 최대 32767이상의 수를 저장할 수 없기 때문이다. 50000이라는 값이 대입되기는 하지만 short형은 최상위 비트를 부호 비트로 해석하기 때문에 음수가 되어 버리는 것이다. 이런 식으로 변수의 저장 용량을 넘어서는 현상을 오버플로우(Overflow)라고 한다.



이런 문제가 발생한 근본적인 원인은 만단위의 수치를 저장하는데 short형을 사용했다는데 있다. a,b,c를 unsigned short형으로만 바꾸어도 위 예제는 제대로 실행된다. 그러나 그렇게 하더라도 65535이상의 수를 저장할 수는 없다. 더 큰 수를 다루려면 int나 unsigned같은 4바이트의 더 큰 타입을 사용해야 한다. int는 20억 정도의 큰 수치를 저장할 수 있으므로 일반적으로 오버플로우 걱정을 하지 않아도 된다.

변수의 표현 범위를 초과하는 현상과 반대로 최소 표현수에 미치지 못하는 경우도 발생할 수 있다. s, t, u는 모두 부호를 표현하지 못하는 unsigned short로 선언되었으며 20000이라는 값을 가지는 s에서 30000이라는 값을 가지는 t를 빼서 u에 대입했다. u에 대입되는 값은 -10000이 아니라 55536이라는 양수값이 되어 버린다. unsigned short형이 표현할 수 있는 최소수는 0인데 이 값보다 더 작은 값을 대입했으므로 계산 결과가 틀려지는 것이다.

수학적인 연산을 할 때는 항상 이 점을 주의해야 한다. 아주 간단할 것 같은 연산도 정확한 타입과 함께 사용해야만 결과가 제대로 나온다. 메모리가 지극히 부족한 상황이 아닌 한은 정수가 필요할 때 부호 있는 4바이트 정수인 int를 사용하면 별 문제가 없다. int는 음양으로 20억이라는 실생활에서 거의 부족하지 않는 정도의 표현 범위를 가지고 있기 때문이다.

정수형 타입의 도표를 보면 int와 long은 크기나 부호 여부가 동일하며 따라서 표현할 수 있는 수의 범위도 완전히 동일하다. 왜 똑같은 타입을 둘 씩이나 정의해 놓았는지 의아하겠지만 이 둘은 엄밀하게 말하면 다른 타입이다. 아니, 다른 타입이라기 보다는 달라질 수 있는 타입이라고 하는 편이 옳을 것 같다.

C 언어의 타입 정의에 int 형은 "CPU의 레지스터와 동일한 크기를 가지는 타입"으로 정의되어 있다. 레지스터란 CPU내의 임시 기억 장소이며 레지스터의 크기에 따라 CPU의 비트 수를 정의한다. 즉, 레지스터가 16비트이면 16비트 컴퓨터, 32비트이면 32비트 컴퓨터라고 부른다. 비트 수가 높으면 높을수록 CPU가 한 번에 처리할 수 있는 자료양이 많아지므로 더 성능이 높다고 할 수 있다.

, int형은 CPU가 가장 효율적으로 다룰 수 있는 정수형으로 정의되어 있으며 그래서 int형의 실제 크기는 플랫폼에 따라 달라진다. 다음에 알아볼 포인터형도 마찬가지이다. 과거 8086이나 80286같은 16비트 CPU 시절, 윈도우즈 3.1같은 16비트 운영체제에서 int는 16비트였었다. 그러나 386이후의 CPU와 윈도우즈 95이후의 32비트 운영체제에서 int는 32비트이다. 64비트 CPU가 나오면(이미 나와 있다) 그때는 int형이 64비트(8바이트)가 될 것이다.

반면 long형은 그 크기가 4바이트로 고정되어 있어 어떤 플랫폼에서나 4바이트이다. int와 long이 동일한 크기를 가지는 것은 32비트 플랫폼에서 뿐이며 16비트에서는 서로 다른 타입이고 64비트에서도 달라질 것이다. 꼭 4바이트를 쓰고 싶으면 long형으로 선언하고 플랫폼의 환경에 따라 적절한 크기를 자동으로 선택하고 싶다면 int형으로 선언하면 된다.

최근 64비트 CPU가 발표되고 점점 더 큰 수를 다룰 일들이 많아지면서부터 C언어도 64비트의 정수를 지원하기 시작했다. 비주얼 C++과 Dev-C++은 __int64라는 타입을 지원하며 이 타입을 사용하면 무려 1800경(264)이라는 엄청난 수를 표현할 수 있다. 다음은 64비트 정수를 사용하여 억단위의 정수끼리 곱해본 것이다.

: int64

#include <Turboc.h>

 

void main()

{

     __int64 a,b,c;

 

     a=111111111;

     b=111111111;

     c=a*b;

     printf("%I64d\n",c);

}


흔히 전자 계산기를 테스트하기 위해 일련의 1을 곱해보는데 12345678987654321이라는 결과가 나오면 제대로 동작하는 것이다. printf로 64비트 정수를 출력하려면 %I64d라는 서식을 사용한다.

Posted by 테티스
,

정수(Integer)란 부호는 있지만 소수점 이하를 표현하지 못하는 수이다. 0, -23, 156 이런 값들은 정수이며 1.28, 25.4 이런 값은 소수점 이하가 있으므로 정수가 아니다. 정수의 정의는 중학교 수학 수준에서 설명되는 것이므로 더 상세한 설명이 필요하지는 않을 것이다. 단, 컴퓨터의 메모리는 유한하기 때문에 수학적 정의와 같은 무한대의 범위를 지원하지 않는다는 정도만 다르다.

정수형이란 이런 정수값을 저장할 수 있는 타입이다. 컴퓨터라는 존재가 원래 정수적인 존재이고 실생활에서 가장 많이 사용되는 수이기 때문에 정수형 타입이 가장 흔하게 사용된다. 정수형 변수의 타입 이름은 Integer의 앞 세 자를 딴 int이다. 따라서 정수형 변수 i를 선언하려면 다음과 같은 선언문을 사용한다.

int i;

키워드 int 다음에 원하는 변수 이름 그리고 세미콜론으로 구성되어 있다. 컴파일러는 이 선언문을 만났을 때 정수값을 저장할만한 4바이트의 공간을 할당하고 이 공간에 대해 i라는 이름을 붙여줄 것이다. 이후 i라는 변수명을 통해 이 메모리에 정수값을 저장할 수 있고 또 값을 읽을 수도 있다. 정수형은 최대 표현 가능한 값의 크기와 부호의 존재 유무에 따라 여러 가지 종류로 나누어진다.

먼저 변수의 크기와 표현 가지수의 관계에 대해 알아보자. 값 하나를 표현하기 위해 몇 비트를 사용할 것인가에 따라 표현 가능한 수의 개수가 달라진다. 예를 들어 1비트로만 구성된 정수형이 있다면 이 정수로는 0과 1의 두 가지 상태밖에 기억하지 못한다. 이런 비트가 두 개 모이면 00, 01, 10, 11 네 가지 각각 다른 상태를 표현할 수 있다. 비트 세 개가 모인다면 각 비트값의 조합에 따라 다음 8가지 상태를 표현할 수 있다. 

이진수

십진수

000

0

001

1

010

2

011

3

100

4

101

5

110

6

111

7


같은 원리로 비트가 4개 모이면 16가지 상태를 표현할 수 있을 것이다. 일반적으로 n개의 비트가 모이면 2n가지의 수를 표현할 수 있으며 0부터 시작하므로 최대 표현 가능한 수는 2n-1이 된다. 8비트로 구성되는 1바이트는 총 256가지 종류의 수를 표현할 수 있고 표현 가능한 최대 수는 255가 되어 0~255까지의 정수를 기억할 수 있다. 2바이트(16비트)라면 216종류의 값을 기억할 수 있고 4바이트(32비트)라면 232 종류의 값을 기억할 수 있을 것이다.

다음은 부호 여부에 따른 표현 범위의 차이를 보자. 부호가 있는 정수(signed)는 제일 왼쪽의 비트(MSB라고 한다)를 부호 비트로 사용하며 이 비트가 0이면 양수이고 1이면 음수가 된다. MSB를 부호 비트로 사용하면 값을 기억하는 비트 하나가 줄어들게 되므로 표현할 수 있는 최대값은 절반으로 줄어드는 대신 음의 값을 표현할 수 있다. 값의 범위가 음수 영역으로 평행이동하는 것이다.



도표를 보면 각 타입별로 할당된 바이트 수와 부호 여부가 다른데 이 크기와 부호 여부에 따라 표현 가능한 수의 범위가 달라진다. 4바이트 크기의 unsigned int는 최대 42억이라는 큰 값을 기억할 수 있는데 비해 2바이트 크기의 unsigned short int는 65535까지만 기억할 수 있다. int형은 부호가 있고 4바이트의 크기를 가지므로 -231~231-1까지의 범위를 가지는데 비해 unsigned형은 같은 4바이트이지만 부호가 없기 때문에 음수를 표현할 수 없는 대신 0~232-1까지 표현할 수 있다. 타입의 이름이 좀 긴데 다음 두 가지 규칙에 의해 좀 더 간략하게 표현할 수도 있다.

부호에 대한 수식어가 생략되면 signed가 적용되어 부호가 있는 것으로 선언된다. signed int는 int와 같고 signed short int는 short int와 같다. 그래서 signed는 보통 붙이지 않는다.

int앞에 수식어가 있을 경우 int는 생략할 수 있다. 그래서 unsigned int는 unsigned로 간단하게 쓸 수 있으며 long int는 long과 같다. 부호있는 4바이트 정수형은 signed int라고 쓰는 것이 원칙이나 signed를 생략하고 int로 쓸 수도 있고 int를 생략하고 signed로 쓸 수도 있다. 그러나 통상 signed라고 쓰지 않고 int라고 간략하게 쓰는 것이 보통이다.

똑같은 정수형에 대해서도 다양한 타입이 준비되어 있는데 이는 상황에 따라 가장 적절한 타입을 선택해서 쓸 수 있도록 하기 위해서이다. 년도나 온도를 기억한다면 2바이트의 short형으로도 충분하므로 굳이 기억 장소를 낭비해 가면서 4바이트나 차지하는 int형을 쓸 필요가 없다. 또한 나이나 성적같이 음수값이 존재하지 않는다면 부호가 없어도 상관없으므로 unsigned형을 쓰는 것이 더 좋다.


 

Posted by 테티스
,