----------------
title : 씹어먹는 C 언어 - <11 - 1. C 언어의 아파트 (배열), 상수>
cat_title : 11 - 1. C 언어의 아파트 (배열), 상수
next_page : 20
publish_date : 2009-11-14
chapter : C 언어의 아파트 - 배열
path : /C
--------------


이번 장에서 배울 내용은

* 배열(Array) 이란 무엇인가? , 배열에 대한 기초적 이해
* 상수는 무엇인가?

입니다.

![씹어먹는 C 언어](/img/ChewingClogo.png)


안녕하세요, 여러분. 지난 강의에 내 주었던 마지막 문제는 모두 푸셨나요? 기본적으로 답안을 제공하고 있지는 않지만, 댓글로 여러분이 어떻게 풀었는지 올려주시는 것은 괜찮습니다.

여태까지 여러분이 C 언어 상에서 메모리를 다룰 때에는 변수를 통해서 메모리에 값을 읽고 쓰고를 할 수 있었습니다. 변수의 이름 하나가, 해당 변수의 타입에 해당하는 만큼의 공간을 메모리에서 나타내고 있죠. 예를 들어서 `int` 는 4 바이트 이니까, `int a` 라는 변수가 있다면 `a` 는 메모리 상에서 4 바이트 공간을 나타내고 있겠죠.

그런데 만약에 여러분이 한 꺼번에 여러 개의 변수들을 다뤄야 한다면 어떨까요? 예를 들어서 `int` 변수가 10 개가 필요하다면 말이죠. 

물론 아래 처럼

```cpp
int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10;
```

이렇게 한다면 가능하겠지만 

1. 매 번 변수 이름들을 저렇게 나열하는 것이 복잡하고
2. 해당 변수들을 가지고 작업 하는 것도 불편합니다. (예를 들어서 위 변수들을 모두 더하려면? `a1 + a2 + ... + a10` 까지 써야 겠죠.)

따라서 C 언어에서는 여러 개의 변수를 한 꺼번에 다루는, 즉 컴퓨터 메모리 상에 같은 타입의 변수를 연속적으로 여러 개를 한 꺼번에 정의할 수 있는 방법을 제공하고 있는데 이를 바로 **배열(Array)** 이라고 합니다.

예를 들어서 `int` 형 배열의 경우, `int` 형 변수들이 메모리 상에 여러 개 존재한다고 보시면 됩니다.

###  배열의 기초

```cpp-formatted
/* 배열 기초 */
#include <stdio.h>
int main() {
  int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

  printf("Array 3 번째 원소 : %d \n ", arr[2]);
  return 0;
}
```

성공적으로 컴파일 하였다면

```exec
Array 3 번째 원소 : 3 
```

와 같이 나오게 됩니다.

배열은 말그대로 **특정한 형(Type) 의 변수들의 집합** 입니다. 이전에 변수를 정의할 때 에는

```info
(변수의 형) (변수의 이름);
```

과 같이 정의했는데 배열은 그와 비슷하게도

```info
(배열의 형) (배열의 이름)[원소 개수];
```

와 같이 해주면 됩니다. 위의 경우

```cpp-formatted
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
```

와 같이 해주었으므로, **`int` 형의 10 개의 원소를 가지는 배열 `arr`!** 이라고 생각하시면 됩니다. 다시말해, 이 배열은 10 개의 `int` 형 변수들을 보관 할 수 있게 됩니다. 

만약 `char arr[10]` 이라 한다면 각 원소들이 모두 `char` 형으로 선언 됩니다. 

또한 위와 같이 배열에 정의 옆에 `= {}` 를 해준다면 배열의 각각의 원소에는 중괄호 속의 각 값들이 순차적으로 들어가게 됩니다. 배열의 첫 번째 원소에는 `1,` 두 번째 원소에는 `2, ... 10` 번째 원소에는 10 이 들어가게 됩니다.

참고로 이 방법은 배열의 정의 부분에서만 가능하고, 이미 정의된 배열에서는 사용할 수 없습니다. 예를 들어서

```cpp
int arr[3] = {1,2,3}; // 가능

arr = {4,5,6}; // 불가능!
```

위 `int arr[3] = {1,2,3}` 는 가능하지만 아래 `arr = {4,5,6};` 는 컴파일 오류 입니다.

한 가지 더 재미있는 점은 배열을 정의할 때 `= {}` 로 원소들을 정의했다면 배열의 크기를 생략하면 원소의 개수에 맞게 컴파일러가 알아서 배열을 정의해줍니다. 예를 들어서

```cpp
int arr[] = {1,2,3,4}; // 크기가 4 인 배열 정의
int arr2[] = {1,2,3,4,5,6} // 크기가 6 인 배열 정의
```

와 같이 되죠.

#### 배열에서의 원소 접근

배열의 각각의 원소들에 접근하는 방법은 간단합니다. 배열의 `n` 번째 원소의 접근하기 위해서는 `arr[n - 1]` 와 같이 써 주시면 됩니다. 즉, 대괄호 `[]` 안에 접근하고자 하는 원소의 **(번째수 - 1)** 을 써주면 되죠. 예를 들어서

```cpp-formatted
printf("Array 3 번째 원소 : %d \n ", arr[2]);
```

이라고 하면 배열의 3 번째 원소인 3 를 출력하게 됩니다. 많은 사람이 헷갈리는 부분인데, `arr[2]` 라고 하게되면 배열의 2 번째 원소인 2 를 출력하게 될 줄이라고 생각하지만 사실 3 번째 원소가 출력되게 됩니다. 다시 말해 `arr[0]` 은 배열의 첫 번째 원소인 1 이 출력되고 `arr[9]` 는 배열의 10 번째 원소인 10 이 출력되게 됩니다. **C 에서 배열의 원소들은 0 번 부터 셈하는 것이라 생각하면 됩니다.**

```lec-warning
배열의 n 번째 원소는 `arr[n - 1]` 입니다. 즉 배열의 첫 번째 원소는 `arr[0]` 입니다.
```

```lec-info
왜 0 번 부터 시작하는지는 뒤에 포인터를 다룰 때 다시 보겠습니다.
```

만일 `arr[10]` 을 출력하려 한다면 무엇을 출력하게 될 까요? 

```cpp
/* 배열 기초 */
#include <stdio.h>
int main() {
  int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

  printf("Array 11 번째 원소? : %d \n ", arr[10]);
  return 0;
}
```

성공적으로 컴파일 하였으면

```exec
Array 11 번째 원소? : 541784832 
```

오잉? 도대체 무슨 값이 나온 것일까요? (경우에 따라서는 아예 오류를 내고 종료될 수 도 있습니다.)

#### 배열의 위험성

이를 이해하기 위해서는 메모리 상에서 배열이 어떻게 있는지 알아야 합니다.

![caption=`arr` 이 메모리 상에서 0x1234 에서 시작한다고 생각해봅시다.](/img/c/11.1.1.png)

위 그림은 `arr` 이 메모리 상에서 어떠한 형태로 배치되어 있는지 보여주는 그림 입니다. 위 경우 배열의 시작 주소가 0x1234 입니다. 

C 의 배열은 단순히 해당 타입의 변수들의 나열이라 생각하면 됩니다. 따라서 배열에는 배열에 크기에 관한 그 어떠한 정보가 없습니다. 다시말해 우리가 `arr[3]` 이렇게 한다면 C 언어 상에서 그냥 **아 배열의 처음 위치로 부터 4 번째 원소구나** 라고 생각하는 것이죠.

![caption=`arr` 의 끝 부분](/img/c/11.1.2.png)

그럼 `arr` 배열의 끝 부분을 살펴봅시다. 위 처럼 메모리 상에 `arr[9]` 가 맨 마지막에 있을 것이고, 그리고 그 뒤 메모리 부분에는 다른 변수들의 데이터가 들어 있겠죠.

하지만 `arr[10]` 를 사용한다면 프로그램 입장에서는 마치 `arr[9]` 뒤에 `arr[10]` 이 있는 것 처럼 생각해서 해당 영역의 값을 보여주게 됩니다. 만일 해당 부분의 메모리 영역이 접근 불가능한 영역이라면, 프로그램은 오류를 내고 종료될 것입니다. 아니라면 해당 부분을 사용하고 있는 데이터의 값이 보여지겠죠.

더 안좋은 일은 해당 부분의 값을 다른 값으로 덮어 씌울 경우 입니다. 예를 들어서 해당 영역을 `b` 라는 변수가 사용하고 있었다고 해봅시다. 그러면 `arr[10] = 3` 을 한다면 마치 `b = 3` 을 한 듯한 효과를 내겠죠? 이는 정말 찾기 힘든 어려운 버그로 이어질 가능성이 높습니다. 따라서 배열을 사용할 때에는 반드시 **우리가 참조하는 원소의 위치가 배열의 크기 보다 작은지 확인** 해야 합니다.

```lec-warning
배열의 원소에 접근할 때에는 반드시 해당 원소의 위치가 배열의 크기 보다 작은지 확인합시다.
```

###  배열 가지고 놀기

이제 본격적으로 배열을 가지고 놀아 보기로 합시다.

```cpp-formatted
/* 배열 출력하기 */
#include <stdio.h>
int main() {
  int arr[10] = {2, 10, 30, 21, 34, 23, 53, 21, 9, 1};
  int i;
  for (i = 0; i < 10; i++) {
    printf("배열의 %d 번째 원소 : %d \n", i + 1, arr[i]);
  }
  return 0;
}
```

성공적으로 컴파일 하였다면

```exec
배열의 1 번째 원소 : 2 
배열의 2 번째 원소 : 10 
배열의 3 번째 원소 : 30 
배열의 4 번째 원소 : 21 
배열의 5 번째 원소 : 34 
배열의 6 번째 원소 : 23 
배열의 7 번째 원소 : 53 
배열의 8 번째 원소 : 21 
배열의 9 번째 원소 : 9 
배열의 10 번째 원소 : 1 
```

아마, 여태까지 배운 내용을 잘 숙지하셨다면 위 소스를 어려움 없이 이해 하셨을 것이라고 생각합니다.

```cpp-formatted
int arr[10] = {2, 10, 30, 21, 34, 23, 53, 21, 9, 1};
```

일단, 배열의 정의 부분. `arr` 라는 원소를 10 개 가지는 `int` 형 배열을 선언한다라는 뜻이지요. 이 때, 각 원소의 값들은 중괄호 속에 있는 값들이 순차적으로 들어가므로 `2, 10, 30 ~ 9, 1` 까지 들어가게 됩니다.

```cpp-formatted
for (i = 0; i < 10; i++) {
  printf("배열의 %d 번째 원소 : %d \n", i + 1, arr[i]);
}
```

 이제, 배열의 원소들의 값들을 출력하는 부분 입니다. 잘 아시다싶이, `for` 문은 `i = 0` 부터 9 까지 1 씩 증가하면서 대입하는데 각 경우의 배열의 `i` 번째 원소를 출력하게 되므로 위 처럼 배열의 원소들의 값들이 출력되게 됩니다. 다시 한 번 기억하세요! 배열의 `n` 번째 원소를 참조하려면 `arr[n-1]` 로 입력해야 합니다!! `arr[n]` 이 '절대로' 아닙니다!

  여기서 우리는 배열의 장점을 알 수 있습니다. 위 프로그램을 배열 없이 작성한다고 생각해보세요. 우리는 10 개의 서로 다른 변수를 만들어서 각각을 출력하는 작업을 해야 했을 것 입니다. 아래 (더러운) 코드는 위 예제와 동일한 내용을 10 개의 변수를 잡아서 직접 출력한 것을 보여 줍니다.

```cpp-formatted
/* 더러운 코드 */
#include <stdio.h>
int main() {
  int a, b, c, d, e, f, g, h, i, j;
  a = 2;
  b = 10;
  c = 30;
  d = 21;
  e = 34;
  f = 23;
  g = 53;
  h = 21;
  i = 9;
  j = 1;

  printf("1 째 값 : %d \n", a);
  printf("2 째 값 : %d \n", b);
  printf("3 째 값 : %d \n", c);
  printf("4 째 값 : %d \n", d);
  printf("5 째 값 : %d \n", e);
  printf("6 째 값 : %d \n", f);
  printf("7 째 값 : %d \n", g);
  printf("8 째 값 : %d \n", h);
  printf("9 째 값 : %d \n", i);
  printf("10 째 값 : %d \n", j);

  return 0;
}
```

만일 여기에서 10 개의 변수의 값을 각각 입력받는 부분이라도 추가하라면 마우스라도 움켜 쥐고 울 것입니다. 하지만 배열을 이용하면 간단히 끝내 버릴 수 있습니다. 아래 예제 처럼 말이지요.

```cpp-formatted
/* 평균 구하기*/
#include <stdio.h>
int main() {
  int arr[5];  // 성적을 저장하는 배열
  int i, ave = 0;

  for (i = 0; i < 5; i++)  // 학생들의 성적을 입력받는 부분
  {
    printf("%d 번째 학생의 성적은? ", i + 1);
    scanf("%d", &arr[i]);
  }
  for (i = 0; i < 5; i++)  // 전체 학생 성적의 합을 구하는 부분
  {
    ave = ave + arr[i];
  }

  // 평균이므로 5 로 나누어 준다.
  printf("전체 학생의 평균은 : %d \n",  ave / 5);  
  return 0;
}
```

성공적으로 컴파일 하였다면

```exec
1 번째 학생의 성적은? 100
2 번째 학생의 성적은? 90
3 번째 학생의 성적은? 80
4 번째 학생의 성적은? 70
5 번째 학생의 성적은? 60
전체 학생의 평균은 : 80 
```

사실, 위 평균 구하는 프로그램은 앞에서도 한 번 만들어 보았는데 이번에는 배열을 이용하여 프로그램을 만들어 보았습니다. 이전보다 오히려 더 복잡해진 느낌이지만, 학생 개개인의 성적을 변수로 보관하기 때문에 더 많은 작업들을 할 수 있게 되죠.

```cpp-formatted
for (i = 0; i < 5; i++)  // 학생들의 성적을 입력받는 부분
{
  printf("%d 번째 학생의 성적은? ", i + 1);
  scanf("%d", &arr[i]);
}
```

  위 소스에서 학생들의 성적을 입력받는 부분 입니다. 별다른 특징이 없습니다. 이전에 `scanf` 에서 &(변수명) 형태로 값을 입력 받았는데 배열도 마찬가지로 같습니다. (이 부분에 대해서는 나중에 좀 더 자세히 다루도록 하지요. 왜 `&(arr[i])` 로 안해도 상관이 없는지...) 배열도, `&arr[i]` 로 쓰면 `arr` 배열의 (i+1) 번째 원소에 입력을 받게 됩니다. 이 때, `arr` 이 `int` 형 배열이기에 각 원소도 모두 `int` 형 이므로 `%d` 를 사용하게 되지요.

```cpp-formatted
for (i = 0; i < 5; i++)  // 전체 학생 성적의 합을 구하는 부분
{
  ave = ave + arr[i];
}
```

 이제 `ave` 에 배열의 각 원소들의 합을 구하는 부분 입니다. 배열도 변수의 모음이기에, 각 원소들은 모두 변수처럼 사용 가능합니다. 물론 배열의 각 원소들끼리의 연산도 가능합니다. 예를 들어서

```info
 ave[4] =  ave[3] + ave[2] * i + ave[1]/i;
```

  와 같이 해도 `ave[4]` 는 정확한 값이 들어가게 되지요. 마지막으로

```cpp-formatted
printf("전체 학생의 평균은 : %d \n", ave / 5);
```

라 하면 전체 학생들의 평균을 구할 수 있게 됩니다.

자, 그렇다면 아까 위의 친구가 부탁했던 프로그램을 만들 수 있는 수준까지 도달할 수 있겠습니다. 한 번 여러분이 짜보고 제가 만든 소스코드와 비교해 보는 것도 좋은 방법 인 것 같습니다. (참고로 저는 학생을 30 명으로 하면 처음에 데이터 입력하기가 너무 힘들어서 그냥 10 명으로 했으니 여러분은 마음대로 하시기 바랍니다.)

```cpp-formatted
/* 친구의 부탁 */
#include <stdio.h>
int main() {
  int arr[10];
  int i, ave = 0;
  for (i = 0; i < 10; i++) {
    printf("%d 번째 학생의 성적은? ", i + 1);
    scanf("%d", &arr[i]);
  }
  for (i = 0; i < 10; i++) {
    ave = ave + arr[i];
  }
  ave = ave / 10;
  printf("전체 학생의 평균은 : %d \n", ave);
  for (i = 0; i < 10; i++) {
    printf("학생 %d : ", i + 1);
    if (arr[i] >= ave)
      printf("합격 \n");
    else
      printf("불합격 \n");
  }
  return 0;
}
```

성공적으로 컴파일 하였다면 아래와 같은 화면을 볼 수 있을 것입니다.

```exec
1 번째 학생의 성적은? 30
2 번째 학생의 성적은? 90
3 번째 학생의 성적은? 80
4 번째 학생의 성적은? 68
5 번째 학생의 성적은? 99
6 번째 학생의 성적은? 100
7 번째 학생의 성적은? 78
8 번째 학생의 성적은? 23
9 번째 학생의 성적은? 85
10 번째 학생의 성적은? 49
전체 학생의 평균은 : 70 
학생 1 : 불합격 
학생 2 : 합격 
학생 3 : 합격 
학생 4 : 불합격 
학생 5 : 합격 
학생 6 : 합격 
학생 7 : 합격 
학생 8 : 불합격 
학생 9 : 합격 
학생 10 : 불합격 
```

유후! 어때요. 잘 출력되는 지요.

```cpp-formatted
for (i = 0; i < 10; i++) {
  printf("학생 %d : ", i + 1);
  if (arr[i] >= ave)
    printf("합격 \n");
  else
    printf("불합격 \n");
}
```

사실, 위 소스는 앞에서 보았던 우리의 평균 구하는 소스에 약간 더해서 만든 것 이므로 위 부분만 살펴 보면 되겠습니다. `i` 가 0 부터 9 까지 가면서 배열의 각 원소들을 `ave` 와 비교하고 있습니다. 만약, `ave` 이상이라면 합격, 그렇지 않다면 불합격을 출력하게 말이지요. 솔직히 이 정도 수준의 프로그램 소스는 이제 더이상 설명해 줄 필요가 없어진 것 같습니다. (저만 그런가요? 만약 그렇지 않다면 이전의 강의들을 다시 한 번 정독하기를 강력하게 권합니다)



###  소수 찾는 프로그램


이번에는 배열을 활용한 프로그램을 하나 더 살펴 보겠습니다. 이번 프로그램은 '배열' 을 활용한 소수 찾는 프로그램 입니다. **소수(prime number)**는 1 과 자신을 제외한 약수가 하나도 없는 수를 일컫습니다.

예를 들어, 2 와 3 은 소수 이지만 4 는 2 가 약수 이므로 소수가 아니지요. 또한 1 도 소수가 아닙니다. 아무튼, 소수를 찾는데 배열을 활용한다는 것은 이전에 찾은 소수들을 배열에 저장하여, 어떠한 수가 소수인지 판별하기 위해 그 수 이하의 소수들로 나누어 본다는 뜻입니다.

만일, 그 수 이하의 모든 소수들로 나누었는데 나누어 떨어지는 것이 없다면 그 수는 소수가 됩니다.또한, 짝수 소수는 2 가 유일하므로 홀수들에 대해서만 계산하도록 합니다 . 또한, 짝수 소수는 2 가 유일하므로 홀수들에 대해서만 계산하도록 합니다.

 이러한 아이디어를 바탕으로 프로그램을 짜 보겠습니다. 여러분은 아래 제가 구현한 코드를 보지 말고 한 번 스스로 해보시기 바랍니다. 참고로 저의 프로그램은 소수를 1000 개 만 찾습니다.

```cpp-formatted
/* 소수 프로그램 */
#include <stdio.h>
int main() {
  /* 우리가 소수인지 판별하고 있는 수 */
  int guess = 5; /* 소수의 배열 */
  int prime[1000]; /*    현재까지 찾은 (소수의 개수 - 1)    아래 두 개의 소수를
                      미리 찾았으므로    초기값은 1 이 된다.    */
  int index = 1; /* for 문 변수 */
  int i;         /* 소수인지 판별위해 쓰이는 변수*/
  int ok;        /* 처음 두 소수는 특별한 경우로 친다 */
  prime[0] = 2;
  prime[1] = 3;
  for (;;) {
    ok = 0;
    for (i = 0; i <= index; i++) {
      if (guess % prime[i] != 0) {
        ok++;
      } else {
        break;
      }
    }
    if (ok == (index + 1)) {
      index++;
      prime[index] = guess;
      printf("소수 : %d \n", prime[index]);
      if (index == 999) break;
    }
    guess += 2;
  }
  return 0;
}
```

성공적으로 컴파일 했다면

```exec
... (생략) ...
소수 : 7727 
소수 : 7741 
소수 : 7753 
소수 : 7757 
소수 : 7759 
소수 : 7789 
소수 : 7793 
소수 : 7817 
소수 : 7823 
소수 : 7829 
소수 : 7841 
소수 : 7853 
소수 : 7867 
소수 : 7873 
소수 : 7877 
소수 : 7879 
소수 : 7883 
소수 : 7901 
소수 : 7907 
소수 : 7919 
```

와 같이 소수가 쭉 나오는 것을 볼 수 있습니다. 일단 위 소스코드의 핵심적인 부분만 설명해 보도록 하겠습니다.

```cpp-formatted
for (i = 0; i <= index; i++) {
  if (guess % prime[i] != 0) {
    ok++;
  } else {
    break;
  }
}
```

위 부분은 `guess` 이하의 모든 소수들로 나누어 보고 있는 작업 입니다. `index` 는 `(배열에 저장된 소수의 개수 - 1)` 인데 `prime[i]` 로 접근하고 있으므로 배열의 모든 소수들로 나누어 보게 됩니다. 

만일 `guess` 가 `prime[i]` 로 나누어 떨어지지 않는다면 `ok` 를 1 증가 시킵니다. 그리고 나누어 떨어진다면 소수가 아니므로 바로 `break` 되서 루프를 빠져 나가게 됩니다. 만일 `ok` 가 `prime` 배열에 저장된 소수의 개수, 즉 `(index + 1)`과 같다면 자기 자신 미만의 모든 소수들로도 안 나누어 떨어진다는 뜻이 되므로 소수가 됩니다.

이 때, 주의해야 할 점은 한 개의 수를 검사할 때 마다 `ok` 가 0 으로 리셋되어야 합니다. 그렇지 않다면 정확한 결과를 얻을 수 없겠죠?

```cpp-formatted
if (ok == (index + 1)) {
  index++;
  prime[index] = guess;
  printf("소수 : %d \n", prime[index]);
  if (index == 999) break;
}
```

따라서, 위와 같이 `index` 를 하나 더 증가시킨 후 `prime[index]` 에 `guess` 를 추가 시켜 줍니다. 만일 `index` 가 999 가 된다면 배열이 꽉 찼단 뜻이 되므로 `break` 를 해서 `for(;;)` 를 빠져 나가게 됩니다. 어때요? 배열을 이용하여 정말 많은 일을 할 수 있지요?



###  배열의 중요한 특징

만약 똑똑한 사람이라면 다음과 같이 생각할 수 있을 것 입니다.

> 처음에 배열의 원소의 수를 숫자로 지정하지 않고 변수로 지정해도 될까? 예를 들어 `arr[i]` 라던지 말이야. 그렇게 된다면 위 프로그램에서 반의 총 학생 수를 입력 받아서 딱 필요한 데이터만 집어 넣으면 되잖아?

그렇다면, 그의 아이디어를 빌려서 프로그램을 만들어 봅시다.

```cpp-formatted
/* 과연 될까? */
#include <stdio.h>
int main() {
  int total;
  printf("전체 학생수 : ");
  scanf("%d", &total);
  int arr[total];
  int i, ave = 0;

  for (i = 0; i < total; i++) {
    printf("%d 번째 학생의 성적은? ", i + 1);
    scanf("%d", &arr[i]);
  }
  for (i = 0; i < total; i++) {
    ave = ave + arr[i];
  }

  ave = ave / total;
  printf("전체 학생의 평균은 : %d \n", ave);

  for (i = 0; i < total; i++) {
    printf("학생 %d : ", i + 1);
    if (arr[i] >= ave)
      printf("합격 \n");
    else
      printf("불합격 \n");
  }

  return 0;
}
```

  만약 컴파일 한다면 아래에 수많은 오류가 쏟아져 나오는 것을 볼 수 있습니다.


![](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile24.uf.tistory.com%2Fimage%2F12553F0C4ABA2FE43E64ED)

왜 오류가 나올까? 라고 고민한다면 [3강](http://itguru.tistory.com/entry/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94-C-%EC%96%B8%EC%96%B4-3-%EB%B3%80%EC%88%98%EA%B0%80-%EB%AD%90%EC%A7%80) 맨 아래 부분을 다시 보시길 바랍니다.\sidenote{2018년 현재, 더이상 변수를 최상단에 선언하지 않아도 됩니다. 아마 오류가 발생하지 않을 것입니다.}

왜냐하면 변수는 무조건 최상단에 선언되야 되기 때문입니다! 위와 같이 배열 `arr` 과 변수 `i, ave` 가 변수 선언문이 아닌 다른 문장 다음에 나타났으므로 C 컴파일러는 무조건 오류로 처리하게 됩니다. (물론 C++ 에서는 가능합니다)

아아. 애초에 사람이 입력하는 대로 배열의 크기를 임의로 정할 수 는 없는 것이였군요. 그렇다면, 그냥 변수 크기 지정시 특정한 값이 들어있는 변수가 가능한지 살펴 봅시다.

```cpp-formatted
/* 설마 이것도? */
#include <stdio.h>
int main() {
  int total = 3;
  int arr[total];
  int i, ave = 0;

  for (i = 0; i < total; i++) {
    printf("%d 번째 학생의 성적은? ", i + 1);
    scanf("%d", &arr[i]);
  }
  for (i = 0; i < total; i++) {
    ave = ave + arr[i];
  }

  ave = ave / total;
  printf("전체 학생의 평균은 : %d \n", ave);

  for (i = 0; i < total; i++) {
    printf("학생 %d : ", i + 1);
    if (arr[i] >= ave)
      printf("합격 \n");
    else
      printf("불합격 \n");
  }

  return 0;
}
```

과연 성공할까요?


![](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile26.uf.tistory.com%2Fimage%2F207CED0C4ABA314D017E44)

아아. 역시 우리의 기대를 처절하게 저버리고 오류를 내뿜는 컴파일러.

이는 C 언어에 처음에 배열의 크기를 변수를 통해 정의할 수 없게 규정하고 있기 때문입니다. (사실, '동적 할당' 이라는 방법으로 억지로 해서 정의할 수 있으나 이 부분에 대한 이야기는 나중에 다루도록 합시다.) 왜냐하면 처음에 컴파일러가 배열을 처리할 대 메모리 상에 공간을 잡아야 하는데 이 때, 잡아야 되는 공간의 크기가 반드시 상수로 주어져야 하기 때문입니다. 지금 수준에서 깊게 설명하는 것은 너무 무리인 것 같으니 그냥 '배열의 크기는 변수로 지정할 수 없다' 정도로 넘어가도록 합시다.



###  상수 (Constant)

상수는 변수의 정반대로 처음 정의시 그 값이 바로 주어지고, 그 값이 영원히 바뀌지 않습니다.

```cpp-formatted
/* 상수 */
#include <stdio.h>
int main() {
  const int a = 3;

  printf("%d", a);
  return 0;
}
```

  만약 성공적으로 컴파일 하였다면

```cpp
3
```

와 같이 나오게 됩니다. 상수는 아래와 같이 정의합니다.

```info
  const (상수의 형) (상수 이름) = (상수의 값);
```

위 소스의 경우, `a` 라는 이름의 `int` 형 상수이고 그 값은 3 이라는 것을 나타내고 있습니다.

```cpp-formatted
const int a = 3;
```

상수라고 해서 꼭 특별한 것이 있는 것은 아닙니다. 단지, 처음에 한 번 저장된 값은 '절대로' 변하지 않는 다는 점일 뿐이지요. 그렇기 때문에 처음 상수를 정의시 값을 정의해 주지 않는다면

```cpp-formatted
#include <stdio.h>
int main() {
  const int a;

  printf("%d", a);
  return 0;
}
```

와 같이 컴파일시 아래와 같이 나타나게 됩니다.


![](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile7.uf.tistory.com%2Fimage%2F18365B194ABA3CF133CD49)

상수는 또한 그 특성 답게 그 값 자체를 바꿀 수 없습니다. 예를 들어서

```cpp-formatted
#include <stdio.h>
int main() {
  const int a = 2;

  a = a + 3;
  printf("%d", a);
  return 0;
}
```

  를 한다면,


![](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile2.uf.tistory.com%2Fimage%2F11435E174ABA3D786ED89C)

  와 같은 오류가 발생하게 됩니다. 즉, 상수는 어떠한 짓으로도 값을 변경할 수 없는 불멸의 데이터 입니다. 이러한 특성 때문에 여러분은 아마 '배열의 크기를 상수로 지정할 수 는 없을까?' 라는 생각을 하게 됩니다. 하지만, 아래와 같은 코드를 보면 이러한 생각이 깨지게 되죠.

```cpp-formatted
#include <stdio.h>
int main() {
  int b = 3;
  const int a = b;
  char c[a];
  return 0;
}
```

즉, 이는 상수 `a` 로 배열의 크기가 할당이 가능하게 된다면 다시 말해 변수 `b` 의 크기로 배열의 크기를 지정할 수 있다는 말이 되기 때문에 이전에 '변수로 배열의 크기를 지정할 수 없다' 라는 사실에 모순 됩니다.

컴퓨터 프로그래밍을 하다 보면 상수를 사용할 일이 꽤나 많습니다. 예를 들어서 계산기 프로그램을 만들 때 에는 `Pi` 의 값을 상수로 지정해 놓게 된다면 변수로 지정할 때 보다 훨씬 안전해지게 됩니다. 왜냐하면 변수로 `Pi` 의 값을 지정했을 때, 프로그래머가 코딩상의 실수로 그 값을 바꾼다면 찾아낼 도리가 없지만, 상수로 지정시에 그 값을 실수로 바꾸도록 코딩을 해도 애초에 컴파일러가 오류를 뿜기 때문에 오류를 미연에 방지할 수 있습니다.



### 초기화 되지 않은 값

우리가 변수의 값을 초기화 하지 않는다면 그 변수는 무슨 값을 가질 까? 라는 생각을 한 분들이 많을 것 같습니다. 0 을가질까요? 아닙니다. 0 도 값 이지 않습니까? 0 을 가진다면 0 이라는 값을 가진다는 것 이지요? 그렇다면 한 번 해보지요.값이 대입되지 않은 변수의 값을 출력해보는 프로그램을 짜보면 아래와 같습니다.

```cpp-formatted
#include <stdio.h>
int main() {
  int arr;

  printf("니 값은 모니 : %d", arr);
  return 0;
}
```

컴파일 해보면 아래와 같은 경고를 볼 수 있습니다.

```compiler-warning
warning C4700: 초기화되지 않은 'arr' 지역 변수를 사용했습니다.
```

아마, 심상치 않지만 그래도 오류가 없으니 실행은 해볼 수 있겠군요. 아마  실행해 보면 아래와 같은 모습을 보실 수 있을 것 입니다.


![무지막지한 오류가!!](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile27.uf.tistory.com%2Fimage%2F207CCB194AF44121121269)

  아니 이럴수가! 변수 `arr` 의 값을 보기위해 값을 출력하려고 했더니만, 런타임 오류(프로그램 실행 중에 발생하는 오류)가발생하군요. 운영체제는 초기화 되지 않은 변수에 대한 접근 자체를 불허하고 있습니다. 이 때문에 우리는 이 변수에 들어있는 값을영영 보지 못하게 됩니다.

```cpp-formatted
/* 초기화 되지 않은 값 */
#include <stdio.h>
int main() {
  int arr[3];
  arr[0] = 1;
  printf("니 값은 모니 : %d", arr[1]);  // arr[0] 이 아닌 arr[1] 을 출력

  return 0;
}
```

  이번에는 배열의 경우를 살펴 봅시다. 상콤한 기분으로 컴파일을 했으나,

```compiler-warning
warning C4700: 초기화되지 않은 'arr' 지역 변수를 사용했습니다.
```

와 같은 경고가 발생합니다. 심지어 앞 예제에서 발생했던 경고와 번호(C7400)와 동일하군요. 불안감에 사로잡혀 실행해보면.. 아니나 다를까 아래와 같은 오류 메세지 창을 보게 됩니다.


![또 오류!!!](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile7.uf.tistory.com%2Fimage%2F1636490D4AF444254CDCC5)

  역시, `arr[1]` 의 값은 정의되어 있지 않기 때문에 이 값을 출력할 생각은 꿈도 꾸지 말라는 것이라는 것이죠. 참으로 야속한 컴퓨터 입니다.

```cpp-formatted
/* 초기화 되지 않은 값 */
#include <stdio.h>
int main() {
  int arr[3] = {1};
  printf("니 값은 모니 : %d", arr[1]);

  return 0;
}
```


이번에는 마지막으로 거의 자포자기 한 심정으로 위 소스를 컴파일 해 봅시다.

```exec
니 값은 모니 : 0
```

오잉! 경고도, 오류도 나타나지 않습니다. 더군다나, 0 이라는 값이 출력되었습니다! 놀랍군요. 우리는 위 문장에서 `arr[1]` 에 0 을 집어 넣는 다는 말은 한 번이라도 하지 않았습니다. 사실 여러분은 위와 같이 배열을 정의한데에 놀랄 수도 있습니다. 배열의 원소는 3 개나 있는데 값은 오직 1 개 밖에 없기 때문이죠. 하지만 여러분들은 위 문장이 다음과 같은 역할을 한다는 것은 직감적으로 알 수 있습니다.

```info
int arr[3];
arr[0] = 1;
```

물론 맞는 말 입니다. `printf` 부분을 `arr[0]` 출력으로 바꾸어 보면 여러분이 생각했던 대로 1 이 출력됩니다. 하지만, `arr[1]` 이 도대체 왜 오류가 나지 않는 것일까요? 아까전에  `int arr[3]; arr[0] = 1;` 방법으로 해서 끔찍한 오류가 발생되는 것을 여러분이 두 눈으로 톡톡히 보셨지 않습니까? 그 이유는

```cpp-formatted
int arr[3] = {1};
```

와 같이 정의한다면 컴파일러가 내부적으로 아래와 같이 생각하기 때문입니다.

```cpp-formatted
int arr[3] = {1, 0, 0};
```

따라서, 자동적으로 우리가 특별히 초기화 하지 않은 원소들에는 0 이 들어가게 됩니다.

그렇다면

```cpp-formatted
int arr2[5] = {1, 2, 3};
```

은 어떻게 될까요? 역시, 해보면

```cpp-formatted
int arr2[5] = {1, 2, 3, 0, 0}
```

과 같이 한 것과 똑같이 됩니다.

이상으로, 배열에 대한 첫 번째 강의를 마치도록 하겠습니다. 아직 여러분은 배열에 대해 모든것을 다 알고 계신 것은 아닙니다. 다음 강의에서는 더욱 놀라운 배열의 기능을 알아 보도록 합시다.

### 생각해 볼 문제

#### 문제 1

위 입력받는 학생들의 성적을 높은 순으로 정렬하는 프로그램을 만들어 보세요.

#### 문제 2

입력받은 학생들의 성적을 막대 그래프로 나타내는 프로그램을 만들어 보세요.

##@ chewing-c-end