모두의 코드
씹어먹는 C 언어 - <3. 변수가 뭐지? >

작성일 : 2009-04-22 이 글은 127102 번 읽혔습니다.

이번 강좌에서 배우게 될 것은

  • 변수란 무엇인가?

  • 정수형, 실수형 변수

  • 16 진법, 메모리 주소

  • 변수 이름 짓기

씹어먹는 C 언어

안녕하세요? 여러분. 잘 지내셨나요. 지난 번에 처음으로 C 코드를 분석한 것은 이해가 잘 되셨나요? 이해가 잘 안되셨다도 괜찮습니다. 점점 C 언어를 배워감에 따라, 기존이 이해가 안 되었던 것들도 언젠가는 '아 이래서 그랬구나' 하는 순간이 오게 됩니다. 이 단계에서 여러분이 취해야 할 자세는 일단 이해가 안되는 것은 일단, 암기 하고, 포기하지 않는 것이 필요합니다.

변수란 무엇인가?

컴퓨터는 많은 내용을 기억 해야 합니다. 정확히 말하면, 컴퓨터의 '메모리' 라는 부분에 전기적인 신호를 써 놓는 것이죠. 컴퓨터가 무엇을 기억해야 되냐고 생각할 수 있지만, 우리가 많이 하는 게임인 스타크레프트만 보아도 일단, 각 유닛의 체력과 마나, 그리고 실시간으로 바뀌는 미네랄과 가스, 뿐만 아니라 유닛의 위치, 유닛의 데미지 등 모든 것을 기억해야지 우리가 게임을 제대로 즐길 수 있게 되겠지요. 만약 컴퓨터가 미네랄의 양을 제대로 기억 못한다면 미네랄이 갑자기 100 에서 0 이 되거나 10 에서 9999 로 바뀌는 참사가 발생합니다.

그렇다면 컴퓨터는 이러한 데이터들을 어떻게 기억할까요? 바로 컴퓨터의 메모리, 즉 램(RAM) 이라는 특별한 기억공간에 이를 기록합니다. 보통 우리는 흔히 램 을 설명할 때 아래 처럼 표시합니다.

마치, 각 방에 데이터들이 저장됩니다. 이 때, 컴퓨터는 각 방에 이름을 붙이는데 단순하게 숫자로 이름을 붙입니다. 0 번, 1 번, 2 번, .. 와 같이 말입니다. 우리 대부분이 사용하는 32 비트 CPU 에서는 최대 2³² 개(4GB) - 총, 42 억개 달하는 방을 가질 수 있게 되겠지요. 참고로 32 비트 숫자를 매번 쓰는게 매우 힘들기 때문에, 대개 16진법으로 주소값을 나타냅니다.

예를 들어서, 컴퓨터가 0x12345678 부터 0x1234567B 부분에 내가 캔 미네랄의 양에 관한 정보를 저장했다고 합시다. (이 한칸에는 1 바이트, 즉 -128 부터 127 까지의 수 데이터를 저장할 수 있습니다) 만약 우리가 건물을 지을 때, 내가 가진 미네랄의 양이 충분한 지 확인하기 위해, 내가 캔 미네랄의 양에 관한 정보가 필요합니다. 그런데, 이렇게 미네랄에 관한 정보가 필요로 할 때 마다 이 길고 알아보기 힘든 복잡한 주소를 일일이 써야 한다면 상당히 힘들겠지요.

하지만 다행히도 C 언어에는 변수 라는 것이 있어서, 이 모든 작업을 쉽게 할 수 있습니다. 예를들어, 내가 캔 미네랄의 양을 mineral 이라는 변수에 저장했다고 합시다. 그렇다면 컴퓨터는 '알아서' 메모리의 어딘가에 mineral 의 방을 주고 그 내용을 저장합니다. 예를들어서, 컴퓨터가 이 mineral 이라는 변수에게 4 칸의 자리를 할당해 주었다고 합시다. 이는 아래 그림처럼 메모리 상에 표시됩니다.

이 때, 우리가 미네랄을 더 캐서 8 을 추가해야한다고 봅시다. 만약 이전에 8 을 추가한다면 0x12345678 부터 0x1234567B 까지의 모든 내용을 불러와서 8 을 더한 후, 다시 집어넣는 작업을 일일이 손으로 써 주어야 되었을 것입니다.

하지만, 이제는 단순히 mineral = mineral + 8 과 같이 써 주기만 한다면 mineral 에 8 이 더해지는 것이죠. (만약 mineral = mineral + 8 이라는 식이 이해가 안되도 그냥 넘어가세요. 이 처럼 간단해 진다는 것을 말해주고 싶었을 뿐입니다)

이와 같이 C 언어에서, 바뀔 수 있는 어떤 값을 보관하는 곳을 변수 라고 합니다. 영어로는 Variable 이라 하는데, 말 그대로 바뀔 수 있는 것들 이라는 뜻입니다.

변수 선언하기

/* 변수 알아보기 */
#include <stdio.h>
int main() {
  int a;
  a = 10;
  printf("a 의 값은 : %d \n", a);
  return 0;
}

프로젝트를 만들어 위의 내용을 적은 후, 컴파일 해봅시다. 까먹었다면 1 강을 참조하세요. 만약 성공적으로 하였다면

실행 결과

a 의 값은 : 10 

와 같이 나옵니다.

일단, 이번에도 역시 생소한 것들이 나왔기 때문에 한 문장씩 차근차근 살펴 봅시다.

int a;

음, 이게 무엇일까요? 이전에 int main() 에서 보았던 int 가 다시 나타났군요.사실 이 문장에 뜻은 a 라는 변수를 우리가 쓰겠다고 컴파일러에게 알리는 것입니다. 만약 이러한 문장이 없다면 우리가 x 가 뭐고 y 가 뭔지 알려주지도 않은 채, 친구에게 x + y 가 얼마냐? 하고 물어보는 것과 똑같은 격이 되는 것이지요.

이 때, a 앞에 붙은 int 라는 것은 int 형의 데이터를 보관한다는 뜻으로, a 에 -2147483648 에서 부터 2147483647 까지의 정수를 보관 할 수 있게 됩니다. (대략 20 억 정도라 기억하시면 됩니다) 따라서, 만약 중간의 문장을

a = 10000000000000;

와 같이 한다면 아마 a 의 값을 출력하였을 때, 이상한 결과가 나오게 됩니다. 왜냐하면 보관할 수 있는 범위를 초과하는 수를 보관했기 때문이죠.

그럼 이제, 걱정이 생깁니다. a 에 고작 10 밖에 안 넣을 거 면서, 굳이 2147483647 까지 표현할 수 있는 int 형의 변수를 왜 사용했냐고 물을 수 있습니다. 물론, int 형 보다 작은 범위의 숫자 데이터 만을 가지는 형식이 있기는 하지만 (char 등등), 일반적인 경우 정수 데이터를 보관할 때 int 형 변수를 사용합니다.

또한, 2147483647 보다 큰 수를 사용하려면 어떻게 해야되냐는 궁금증도 생기지요. 물론 이 보다도 훨씬 큰 숫자를 처리하는 데이터 형식이 있습니다. 아래의 표를 참조하세요.

타입

크기

범위

char

1 바이트

unsigned char 의 경우 0 부터 255, signed char (그냥 char) 의 경우 -128 부터 127 까지

short

최소 2 바이트

2 바이트일 경우 signed short 는 -32,768 에서 32,767. unsigned short 는 0 부터 65,535 까지

int

최소 2 바이트 이고 보통 시스템의 경우 4 바이트로 구현된다.

4 바이트 일 경우 signed int 의 경우 -2,147,483,648 에서 2,147,483,647 까지. unsigned int 의 경우 0 부터 4,294,967,295 까지

long

최소 4 바이트. 32 비트 시스템의 경우 4 바이트, 64 비트 시스템에선 보통 8 바이트

long long

최소 8 바이트

8 바이트일 경우 signed long long 는 -9223372036854775808 부터 9223372036854775807 까지. unsigned long long 는 0 에서 18446744073709551615 (대략 $1.84 \times 10^{19}$) 까지

float

4 바이트

$\pm 1.2 \times 10^{-38}$ 부터 $\pm 3.4 \times 10^{34}$ 까지. 정밀도는 대략 10진수로 6 자리 정도

double

8 바이트

$\pm 2.3 \times 10^{-308}$ 부터 $\pm 1.7 \times 10^{308}$ 까지. 정밀도는 대략 10진수로 15 자리 정도

한 가지 중요한 점은 각 타입들의 크기는 char, float, double 말고 정확히 정해진 것이 없습니다. 예를 들어서 int 의 경우 C 표준을 읽어보면 최소 2 바이트 인 타입 이라고 써져 있지 몇 바이트로 해라~ 라고 써있지는 않습니다. 하지만 대부분의 시스템에서 int 는 4 바이트로 구현되어 있습니다.

세번째 열인 범위를 보시면, unsignedsigned 라고 나뉜 것이 있는데, 보통 int 라 하면 signed int 를 뜻합니다. 이는 음수와 양수 모두 표시할 수 있는 대신에 양수로 표현할 수 있는 범위가 줄어듭니다.

반면에 unsigned int 는 양수만을 표현할 수 있는 대신에, 양수로 표현할 수 있는 범위가 두 배로 늘어납니다. 또한 마지막에 보면 float, double, long double 이 있는데 이들은 '실수형' 자료형으로 소수(0.1, 1.4123 등) 을 표현 할 수 있습니다. 뿐만 아니라 double 의 경우, $ \pm 2.3 \times 10^{-308} \backsim \pm 1.7 \times 10^{308} $ 의 수들을 표현 할 수 있습니다. (이에 대한 정확한 설명은 후에 다루겠습니다.)

a = 10;

위 문장은 무엇을 의미할까요? 언뜻 보기에도 감이 오시겠지만, 변수 a 에 10 을 집어넣는 다는 것입니다. 따라서 나중에 a 의 값을 출력한다면 10 이 나올 것입니다. 이와 같은 형태의 문장은 뒤에서 연산자에 대해 다룰 때 다시 알아보도록 하겠습니다.

printf("a 의 값은 : %d \n", a);

마지막으로, 지난번에도 보았던 printf 입니다. 그런데, 약간 다른 것이 있습니다. %d 가 출력되는 부분에 써져 있습니다. 그런데, 프로그램을 실행시켜 보았을 때 %d 는 컴퓨터에서 출력되지 않았습니다.

그 대신, %d 가 출력될 자리에 무언가 다른 것이 출력되었는데, 바로 a 의 값 입니다. 따라서,%da 의 값 (정확히는 처음 "" 다음에 오는 첫 번째 변수) 을 10 진수 로 출력하라 라는 뜻이 됩니다.

또 다른 예제를 봅시다.

/* 변수 알아보기 2*/
#include <stdio.h>
int main() {
  int a;
  a = 127;
  printf("a 의 값은 %d 진수로 %o 입니다. \n", 8, a);
  printf("a 의 값은 %d 진수로 %d 입니다. \n", 10, a);
  printf("a 의 값은 %d 진수로 %x 입니다. \n", 16, a);
  return 0;
}

프로그램을 제대로 짰다면 아래와 같은 결과를 볼 수 있을 것입니다.

실행 결과

a 의 값은 8 진수로 177 입니다. 
a 의 값은 10 진수로 127 입니다. 
a 의 값은 16 진수로 7f 입니다. 

일단, 위 코드를 보고 생기는 궁금증은 2 가지 있습니다. % 달린게 2 개나 있는데, 이를 어떻게 해야되냐와, %d 말고도 %o%x 는 무엇인가 입니다.

먼저, printf 의 작동 원리에 대해 봅시다.

printf 출력시에, 큰 따옴표로 묶인 부분 뒤에 나열된 인자들 (8, a) 가 순서대로 큰 따옴표 안의 % 부분으로 들어감을 알 수 있습니다. 따라서 , 예를들면 printf("%d %d %d %d", a,b,c,d); 와 같은 문장은 a, b, c, d 의 값이 순서대로 출력되겠죠.

이제, %o%x 는 무엇일까요? 이는 인자(a)의 값을 출력하는 형식 입니다. 즉, %oa 의 값을 8 진수로 출력하라라는 뜻이고, %x 는 16 진수로 출력하라는 뜻 이죠.

실수형 변수

앞서 말했듯이, 실수형에는 floatdouble 이 있습니다. double 의 경우 int 형에 비해 덩치가 2 배나 크지만 그 만큼 엄청난 크기의 숫자를 다룰 수 있습니다. 그 대신, 처음 15 개의 숫자들만 정확하고 나머지는 10 의 지수 형태로 표현됩니다. 또한 floatdouble 의 장점은 소수를 표시할 수 있다는 점인데, 정수형 변수에서 소수를 넣는다면 (예를들어 int a; a = 1.234;), 소수 부분은 다 잘린 채, 나중에 a 의 값을 표시해 보면 1 이 나올 것 입니다.

/* 변수 알아보기 3*/
#include <stdio.h>
int main() {
  float a = 3.141592f;
  double b = 3.141592;
  printf("a : %f \n", a);
  printf("b : %f \n", b);
  return 0;
}

실행해 본다면 아래와 같이 나오게 됩니다.

실행 결과

a : 3.141592 
b : 3.141592 

일단, 위 코드를 보면서 궁금한 점이 생기지 않았나요?

float a = 3.141592f;
double b = 3.141592;

왜, float 형 변수 a 를 선언할 때 에는 숫자 뒤에 f 를 붙였는데 double 형 에서는 f 를 안 붙였는 지요. 왜냐하면, 그냥 f 를 안 붙이고 float a = 3.141592 로 하면 이를 double 형으로 인식하여 문제가 생길 수 있습니다. 따라서, float 형이라는 것을 확실히 표시해 주기 위해 f 를 끝에 붙이는 것입니다.

printf("a : %f \n", a);
printf("b : %f \n", b);

이제, 마지막으로 %d, %o, %x 도 아닌 %f 가 등장하였습니다. 만약, 여기서 a%d 형식으로 출력하면 어떻게 될까요? 한 번 해보세요. 아마 이상한 숫자가 나오게 될 것입니다. 왜냐하면 a 는 지금 정수형 변수가 아니기 때문 입니다. 설사, 우리가 a = 3f; b = 3; 라고 해도, 이미 ab 를 실수형 변수로 선언하였기 때문에 컴퓨터는a ,b 를 절대 정수로 보지 않습니다.

따라서, 우리는 실수형 변수를 출력하는 형식인 %f 를 사용해야 합니다.

참고로 주의할 사항은 printf 에서 %f 를 이용해 수를 출력 할 때 다음과 같이 언제나 소수점을 뒤에 붙여 주어야 한다는 점입니다. 예를 들어서

printf("%f", 1);

을 하면 화면에 이상한 값 (아마도 0 이 출력될 것입니다) 이 나오지만

printf("%f", 1.0);

을 하면 화면에 제대로 1.0 이 출력됩니다.

printf 의 또 다른 형식

/* printf 형식 */
#include <stdio.h>
int main() {
  float a = 3.141592f;
  double b = 3.141592;
  int c = 123;
  printf("a : %.2f \n", a);
  printf("c : %5d \n", c);
  printf("b : %6.3f \n", b);
  return 0;
}

만약 위 소스를 성공적으로 쳤다면 실행시 아래와 같이 나오게 됩니다.

실행 결과

a : 3.14 
c :   123 
b :  3.142 
printf("a : %.2f \n", a);

이번에는 %f 가 아니라 %.2f 로 약간 다릅니다. 그렇다면 .2 가 뜻 하는 것은 무엇일까요? 대충 짐작했듯이, 무조건 소수점 이하 둘째 자리 까지만 표시하라 란 뜻입니다. 따라서, 위의 경우 3.1415923.14 까지만 출력되고 나머지는 잘리게 되죠.

여기서 '무조건' 이라는 것은 %.100f 로 할 경우에도, 3.141592000000....00 을 표시해서 무조건 100 개를 출력하게 합니다.

printf("c : %5d \n", c);

이번에는 %d 가 아닌 %5d 입니다. 여기서 .5 가 아님을 주의합시다. 이 말은, 숫자의 자리수를 되도록 5 자리로 맞추라는 것입니다. 따라서, 123 을 표시할 때, 5 자리를 맞추어야 하므로 앞에 공백을 남기고 그 뒤에 123 을 표시했습니다.

그런데, 123456 을 표시할 때, %5d 조건을 준다면 어떻할까요? 이 때는 그냥 123456 을 다 표시합니다. 앞서 .?f? 의 수 만큼 무조건 소수점 자리수를 맞추어야 하지만 이 경우는 반드시 지켜야 되는 것은 아닙니다

printf("b : %6.3f \n", b);

마지막으로, 위에서 썼던 두 가지 형식을 모두 한꺼번에 적용한 모습입니다. 전체 자리수는 6 자리로 맞추되 반드시 소수점 이하 3 째 자리 까지만 표시한다는 뜻입니다.

변수 작명하기

앞서, 보았듯이 변수를 선언하는 것은 어려운 일이 아닙니다. 단지, 아래의 형태로 맞추어 주기만 하면 됩니다.

(변수의 자료형) 변수1, 변수2, .....;
/* 예를 들어 */
int a, b, c, hi;
float d, e, f, bravo;
double g, programming;
long h;
short i;
char j, k, hello, mineral;

이 때, 변수 선언시 주의해야 할 점이 있습니다. 만약에 여러분이 오래된 버전의 C 언어 (C89) 를 사용한다면, 변수 선언시 반드시 최상단에 위치해야 합니다. 하지만, 여러분이 지금 사용하고 있는 최신 버전의 C 의 변수 사용하기 전 아무데나 변수를 선언해도 상관 없습니다.

/* 변수 선언시 주의해야 할 점 */
#include <stdio.h>
int main() {
  int a;
  a = 1;
  printf("a 는 : %d", a);
  int b;  // 괜찮음!
  return 0;
}

두 번째로, 사람의 이름을 지을 때, 여러가지를 고려하듯이 변수의 이름에서도 여러가지 조건들이 있습니다. 아래 예제를 보세요.

/* 변수 선언시 주의해야 할 점 */
#include <stdio.h>
int main() {
  int a, A;  // a 와 A 는 각기 다른 변수 입니다.
  int 1hi;
  // (오류) 숫자가 앞에 위치할 수 없습니다.
  int hi123, h123i, h1234324;  // 숫자가 뒤에 위치하면 괜찮습니다.
  int 한글이좋아;
  /*
  (오류)
  변수는 오직 알파벳, 숫자, 그리고 _ (underscore)로만으로 이루어져야 합니다. */
  int space bar;
  /*
  (오류)
  변수의 이름에는 띄어쓰기하면 안됩니다.  그 대신 _ 로 대체하는 것이 읽기
  좋습니다.*/
  int space_bar;  // 이것은 괜찮습니다.
  int enum, long, double, int;
  /* (오류)
     지금 나열한 이름들은 모두 '예약어' 로 C 언어에서 이미 쓰이고 있는
     것들입니다. 따라서 이러한 것들은 쓰면 안됩니다. 이를 구분하는 방법은
     예약어들을 모두 외우거나 '파란색' 으로 표시된 것들은 모두 예약어라 볼 수
     있습니다   */

  return 0;
}

이 안에 모든 내용이 들어 있습니다. 변수의 이름은 반드시

  • 숫자가 앞에 위치하면 안됩니다. 그러나 중간이나 뒤는 괜찮습니다.

  • 변수명은 오직 영어, 숫자, _ 로 만 구성되어 있어야 합니다.

  • 변수의 이름에 띄어쓰기가 있으면 안됩니다.

  • 변수의 이름이 C 언어 예약어 이면 안됩니다. 보통 예약어를 쓰면 에디터에서 다른 색깔로 표시되어 예약어를 썼는지 안썼는지 알 수 있습니다.

또한 C 언어는 대소문자를 구분합니다(이를 영어로 case sensitive 하다고 합니다). 따라서, VARiableVariable 은 다른 변수 입니다. 왠지, 조건이 많아 변수명을 지을 때, 까다로울 것 같지만 그냥 평범하게 짓다보면 예약어와 겹칠일 도 없고, 숫자가 앞에 오는 경우도 별로 없습니다.

좋은 변수 이름

프로그래밍을 하다보면 변수를 정의할 일이 굉장히 많습니다. 그리고 프로그램의 크기가 커지면 커질 수 록 지어야 할 변수의 이름의 양도 늘어나겠죠. 따라서 귀찮다고 변수의 이름을 아무렇게나 짓는 경우가 있습니다.

int a, b, c;

예를 들어서 위 처럼 변수들을 정의하였다고 생각해봅시다. 코드를 읽는 사람 입장에서 위 a, b, c 가 무슨 일을 하는지 알 수 있을까요? 아닙니다. 코드를 꼼꼼히 읽어보아야지 쟤네들이 무슨일을 하고 있는지 알 수 있겠죠.

하지만

int num_students, total_score;

위 처럼 정의한 경우 num_students 는 학생들의 수를 보관하는 변수구나. 아니면 total_score 는 전체 점수를 보관하는 변수구나 라고 단번에 이해할 수 있습니다. 그러면 그 뒤에 코드를 이해하는 것도 매우 편하구요. 이렇듯 변수들의 이름을 잘 짓는 것은 좋은 코드를 작성하는데 꿰어야할 첫 단추라 보시면 됩니다.

제가 생각할 때 좋은 변수 이름의 기준은 다음과 같습니다.

  • 무슨 데이터를 보관하는지 알 수 있어야 한다.

  • (되도록이면) 영어로 읽히도록 해야 한다. 한국말을 영어로 풀어쓰는 경우도 종종 있는데 이해하기 어렵습니다.

  • 한 가지 스타일을 고수하자.

여기서 한 가지 스타일이란, 흔히 변수들의 이름을 표현하는데 두 가지 방식이 사용됩니다. 하나는 띄어쓰기를 _ 로 나타내는 방식으로

int this_is_some_variable;

위와 같습니다. 다른 하나는 Camel case 라는 방식으로, 대소문자로 이용해 구분하는 방식입니다.

int ThisIsSomeVariable;

글자의 높낮이가 마치 낙타의 등 같다고 해서 Camel case 라고 불리는데, 위 두 가지 방식 모두 많이 사용됩니다. 다만 한 프로그램에서 _ 를 사용하겠다고 했으면 쭉 그 방식대로 가고, Camel case 를 사용하겠다고 했으면 쭉 그 방식으로 가야지 두 가지를 혼용하면 코드를 읽는 사람 입장에서 매우 혼란스럽습니다.

아무튼 위 점을 명시하고 코드를 작성한다면 읽기 쉬운 코드를 작성하는데 도움이 될 것입니다.

자, 이제 우리는 C 언어에서 중요한 부분인 변수에 대해서 알아보았습니다. 현재 우리는 수를 다루는 변수들만 다루었지만, 다음 강좌에서는 변수에 대한 산술 연산과, 문자를 다루는 변수에 대해 알아보도록 하겠습니다.

뭘 배웠지?

변수는 데이터를 임시로 저장하는 곳이며 자유롭게 쓰고 지울 수 있습니다.

각 변수에는 형(type) 이 있어서 해당 형에 맞는 데이터를 보관할 수 있습니다.

변수의 형으로는 정수값을 보관하는 char, int 등이 있고, 실수값을 보관하는 floatdouble 이 있습니다. 각각의 형들은 저장하는 데이터의 크기가 다릅니다.

int a = 10; 의 문장의 의미는 a 라는 정수형 변수를 정의한 뒤에, 해당 변수에 10 의 값을 대입한다 라는 뜻입니다.

변수의 이름을 정하기 위해서는 여러가지 규칙이 있습니다. 이 규칙에 알맞게 변수의 이름을 정해야 되며 그렇지 않을 경우 컴파일 오류가 발생합니다.

강좌를 보다가 조금이라도 궁금한 것이나 이상한 점이 있다면 꼭 댓글을 남겨주시기 바랍니다. 그 외에도 강좌에 관련된 것이라면 어떠한 것도 질문해 주셔도 상관 없습니다. 생각해 볼 문제도 정 모르겠다면 댓글을 달아주세요.

현재 여러분이 보신 강좌는 <씹어먹는 C 언어 - <3. 변수가 뭐지? >> 입니다. 이번 강좌의 모든 예제들의 코드를 보지 않고 짤 수준까지 강좌를 읽어 보시기 전까지 다음 강좌로 넘어가지 말아주세요
댓글이 462 개 있습니다!
프로필 사진 없음
강좌에 관련 없이 궁금한 내용은 여기를 사용해주세요

    댓글을 불러오는 중입니다..