C 프로그래밍 입문/데이터 배열

위키책, 위키책

데이터 배열[+/-]

서너 개의 데이터를 다룰 때엔 변수를 이용하면 충분히 데이터를 저장하거나 관리하는 것이 가능하지만 수십~수백의 데이터를 다룬다던가, 수천~수만의 데이터를 다룰때는 변수를 이용하는 것은 배보다 배꼽이 더 큰 작업이 될 뿐 아니라 프로그램 코드를 불필요하게 크게 만든다는 문제가 있다. 예를 들자면 스터디 그룹내의 다섯친구들의 국어 성적을 저장하고 관리하는 작업이야 어떻게 변수를 이용할 수 있겠지만 반 전체나 학교 전체의 국어 성적을 저장하고 관리하는 작업을 변수로 처리하려는 행동은 프로그램화를 하려는 시도 자체를 포기하게 만들고 말 것이다. 이때 사용할 수 있는 구조가 바로 배열이다.

단순 배열[+/-]

배열은 '변수의 아파트'라고 생각하면 된다. 변수 하나는 하나의 이름을 가지고 있지만, 배열은 여러개의 변수가 하나의 이름과 일련번호를 사용한다. 다음은 float 형 변수 열 개를 갖는 배열의 선언 방법이다.

float data[10];

위와 같이 선언하면 float 형 데이터가 저장될 수 있는 공간이 10개가 연이은 메모리 공간내에 만들어 지고, 10개의 메모리 공간의 첫번째 위치에 data라는 이름이 달리게 된다. 배열을 선언한 경우에는 배열내 몇번째 변수인지를 나타내기 위해 인덱스(index)라는 개념을 사용하며, 이는 배열내 몇번째 변수인지를 일련 번호로 표시 하는 것이다. C 에서 사용되는 배열의 인덱스는 0에서 시작되며, 배열내 멤버의 수가 10개 라면, C 배열의 인덱스는 0에서 9 까지가 된다. 그래서 배열 data의 첫 번째 값과 두번째 값을 더해서 세번째에 넣는다는 프로그램 코드는 다음과 같다.

float data[10];
... 중간 생략 ...
data[2] = data[0] + data[1];

다시한번 말하지만, C 에서 인덱스는 1이 아니라 0에서 부터 시작된다.


그럼 이제 내부에서는 어떻게 동작하는지를 설명 하겠다. 위의 코드에서 선언된 data 라는 이름이 달린 메모리 공간은 메모리의 n 번째 메모리 공간이라 가정해 보자. 다른 변수와는 달리 배열형 변수는 데이터 열(a sequence of data)이라는 개념이 사용 된다. ' n 번째 메모리 공간에 있는 float 형 데이터와 n + 4 번째 메모리 공간에 있는 float 형 데이터를 대상으로 덧셈을 한다. '라는 형태로 기술을 한다. 이때 float 형 데이터가 메모리 공간에 저장될 때 차지하는 메모리 공간은 4바이트 라는 사실은 알려져 있기 때문에 프로그램 코드를 작성하는 사람이 일일히 계산하지 않아도 컴파일러가 대신 처리해 줄 수 있을 것이다.

그래서 메모리 공간 n은 data라는 이름으로 사용되고, 메모리 공간 n 에서 시작되는 float 형 공간에 값을 넣을땐 ' data[0] = 3.1416 '이 되는 것이다. 그리고, 앞에서 말했듯이 float 형 데이터는 메모리를 4바이트 차지한다는 것을 컴파일러도 알고 있으므로, 굳이 +4 라고 하지 않고, 'float 형 데이터의 넓이 * 1'이라는 개념을 사용할 수 있다. 그래서 '메모리 위치 data에서 4 바이트 떨어진 메모리 공간'을 ' data[1] '이라고 쓰는 것 이다.

배열의 초기화[+/-]

1차 배열을 선언과 함께 초기화 할 때에는 '데이터 리스트'라는 것을 사용한다. 데이터 리스트는 말 그대로 하나이상의 데이터를 중괄호({, }; brace)를 사용해서 묶어주는 것을 말한다. 아래 코드는 10개의 배열을 선언과 동시에 초기화 하는 동작을 수행한다.

int iarr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

아래의 코드 역시 위와 동일한 동작을 수행한다.

int iarr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

위의 코드에서는 배열의 길이를 명시적으로 지정하지 않았는데, 실제로 컴파일러가 배열에 들어갈 데이터의 갯수를 세어서 데이터를 저장할 공간을 확보하게 한다. 위와같은 코드는 데이터를 저장하기 위한 메모리를 굳이 명시적으로 확보하지 않아도 되는 경우에 유용하게 사용된다. 주의할 점은 위와 같은 코드를 이용해서 메모리를 데이터 갯수를 기준으로 확보하는 경우 프로그램을 실행 할 때 그 크기가 변하지 않는 다는 점이다. 위 코드에 의해 할당되는 메모리의 크기는 컴파일 할 때 결정된다. 그러므로 아래와 같은 코드는 사용할 수 없다.

 int iarr[] = { };

마지막으로 아래의 코드와 같이 작성한 경우, 할당된 메모리는 10개지만 실제로 초기화는 5개만 했으므로 처음 5개만 지정된 값으로 초기화 되고 나머지 다섯개의 메모리 공간은 0으로 초기화 된다.

int iarr[10] = { 1, 2, 3, 4, 5 };
참고:

초기화 되지 않은 변수의 경우 그 변수가 저장되는 위치에 따라 초기화 되는 조건이 다르다.

스택 영역에 저장되는 지역 자동변수(local auto variable)의 경우 초기화 되지 않은 변수의 값은 어떤 값이 될지 예측할 수 없으며, 일반적으로 쓰레기값(garbage value) 라는 표현을 사용한다. 일반적으로 스택은 여러 함수에서 공유해서 사용하며, 함수가 호출되는 순서나 시점에 따라 직전에 호출 되었던 함수가 지역 자동변수를 저장하기 위해 사용했던 공간을 그대로 다시 사용하기 때문에 어떤 값이 저장되었을 지 예측이 불가능하다. 그렇기 때문에 초기화 되지 않은 자동변수의 값을 바로 읽는 코드를 작성 한다면 상황에 따라 다른 결과를 보이는 프로그램이 만들어지게 되며, 이러한 버그는 찾아내기가 아주 어렵기 때문에 가능하면 초기화 하는 습관을 들이는 것이 좋다.

스태틱 데이터 영역을 사용하는 전역변수, 스태틱 변수는 초기화 하지 않았다면 컴파일시에 컴파일러가 0으로 초기화해준다.
참고:

함수 내에서 선언되어 사용되는 배열의 경우 배열 자체가 스택 영역에 생성되고, 함수가 호출될 때 마다 해당 배열을 위한 메모리를 새로 생성하고 초기화 하는 작업을 하기 때문에 배열의 크기가 큰 경우에는 다른 방법을 사용해야 한다.

함수 내에서 크기가 큰 배열을 생성하는 경우 다음과 같은 문제점을 일으킬 수 있다:

  • 배열을 초기화 하는 경우 함수가 호출될 때 마다 값을 채워넣어야 하기 때문에 프로그램의 수행 속도가 느려질 수 있다. 특히나 자주 호출되는 함수인 경우에는 프로그램의 속도를 치명적으로 느리게 만들 수 도 있다.
  • 이론적으로 스택 -- 지역 자동변수가 저장되는 영역의 크기는 제한되지 않지만, 시스템에 따라 하드웨어적인 제한이나 스택 운영의 효율을 높이기 위해 스택의 크기를 제한하기도 한다. 그렇기 때문에 함수 내에서 선언되는 배열의 크기가 과도하게 큰 경우 스택 오버플로우 에러가 발생하고 프로그램이 강제 종료 될 수 도 있다.
  • 컴파일러의 종류나 옵션에 따라서는 컴파일된 프로그램의 크기가 커질 수 도 있다.

그렇기 때문에 배열의 크기가 조금이라도 크다는 느낌이 든다면[1] 다른 메모리 영역을 사용하는 것을 고려해 볼 필요가 있다:

  • 스태틱 영역을 사용하는 변수 - 전역 변수나 스태틱 변수를 사용한다.
  • alloc() 계열의 함수를 이용하여 힙영역을 할당하여 사용한다.


다차배열[+/-]

앞에서 배열을 설명하기 위해 '전교생의 국어성적'이라는 대 단위 데이터에 대하여 언급 했었다. 그러나 앞서 언급된 배열만 가지고는 '전교생의 국어성적'을 저장하기엔 무언가 부족한 점이 있다. 실제로, 앞에서 설명한 배열만 가지고는 '한 반의 국어성적'을 처리할 수 있을 뿐 그 이상은 다루기가 어렵다. 물론 1반을 위한 배열, 2반을 위한 배열, 3반을 위한 배열... 이라는 식으로 반 갯수 만큼의 배열을 따로 만들어서 프로그램 코드를 만드는 방법도 있겠지만, 아무래도 각각의 경우를 따져서 코드를 작성해야 하므로 프로그램의 크기도 커지고 실수를 할 확률도 높아지게 될 것이다.

그래서 사용할 수 있는 방법이 2차 배열 이라는 개념이다. 먼저 한반에 최대 50명 까지 있는 15개 반을 위한 배열 선언은 다음과 같이 하면 된다:

float data[15][50];

위와 같이 선언을 해주면 '15개의 반에 한 반당 50명씩'의 국어 성적을 저장 할 수 있는 배열이 만들어 진다. 여기에서 한 가지 개념만 이해하면 C 언어를 다루기 편해진다. 위의 선언을 메모리 관점에서 다른 표현을 사용해 보겠다.

'15개의 영역을 연이어 생성한다. 각각의 영역 안에는 50개의 영역이 연이어 존재하며 각각의 영역에는 float 형 데이터가 저장될 수 있다'

아래의 그림은 float[5][5] 배열에 해당되는 메모리 구조이다. 위의 샘플에서 제시한 배열을 그림으로 그리기엔 너무 큰 그림이 될꺼 같아서 부득이 배열을 변경 하였다.

개념상의 5x5 배열
개념상의 5x5 배열
메모리에 배치된 5x5 배열
메모리에 배치된 5x5 배열

위와같이 메모리에 배열이 배치된다. 그림을 보면 금방 깨달을 수 있겠지만 2차 배열이란 결국 배열의 배열을 말한다. 일반적으로 프로그래밍을 할 때엔 두 그림중 위쪽 그림과 같이 평면으로 2차원 배열을 간주하고 프로그래밍을 하게 되며, 'iarr[행][열]' 라는 형태의 좌표계로 생각하고 작업을 하게 되며, 실제 프로그래밍을 할 때 메모리와 관련된 작업을 수행할 때엔 아래쪽 그림과 같은 메모리 구조를 머릿속에 넣고 작업을 하면 된다. 아래쪽 그림을 보면 실제 메모리에 배치될 때엔 float[25] 배열과 float[5][5] 배열간에는 차이가 없다는 것을 알 수 있을 것 이다. 두 배열의 차이점은 단지 다루는 방법이 다를 뿐 이다. 나중에 포인터에 대해 설명할 때 이 특성에 대해 다시한번 언급해 보도록 하겠다.

배열을 2차배열로 확장할 수 있게 되었다면, 동일한 방법으로 2차 배열을 확장할 수 있다. 2차 배열이 1차 배열의 배열 이었듯이, 3차 배열은 2차 배열의 배열이다. 실생활의 이미지 상으로는 2차 배열을 쌓아올린 3차원 입체가 될 것이다. 동일한 방법으로 다차원 배열을 생성할 수 있다. 이론상으로는 무한 차원의 배열을 만들 수 있지만 실제로는 배열의 차원이 제한될 가능성이 전혀 없는 것은 아니고, 배열 자체의 최대 크기는 여러가지 이유에 의해 제한 된다.[2] 그렇기 때문에 너무 큰 배열을 할당하는 것은 그다지 좋은 프로그래밍 습관이 아니다. 일정크기 이상의 배열은 로컬 오토 변수로 선언하지 말고 스태틱 변수나 전역 변수로 선언해서 사용하는 것이 stack overflow 에러를 발생시킬 확률을 제거하는 길이다. 사실 전역 변수나 스태틱 변수를 사용하여 배열을 만드는 것 보다 더 좋은 방법은 alloc() 계열의 함수를 이용해서 힙 영역을 이용하는 것이다.


다차 배열의 초기화[+/-]

다차 배열을 초기화 하는 것은 다차배열의 개념과 앞서 설명한 일차 배열을 초기화 하는 것을 응용하면 간단히 해결이 된다. 일단 2차 배열은 '배열의 배열'이라는 개념이고, 1차 배열을 초기화 하기위해서는 중괄호({, })로 데이터 리스트를 묶어서 초기화를 했었다. 결국 2차 배열을 초기화 하기 위해서는 배열들을 중괄호로 묶어서 초기화 해주면 된다는 의미 이므로, 중괄호들을 중괄호로 묶어서 초기화 해주면 된다. 다음의 코드를 보자:

int i2arr[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };

위와 같이 코드를 작성해 주면 3 x 3 배열이 초기화 될 수 있다. 추가적으로, 2차 배열이란 1차 배열의 배열이며, 배열은 같은 종류의 배열이 나열되어 있는 것 이므로 실제로 다음과 같이 초기화 할 수 도 있다.

int i2arr[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
참고:

조금만 생각해보면 2차 배열은 배열의 배열이고, 배열들이 나열되어 있는 것 이기 때문에 다음과 같은 코드를 작성 할 수 있을 것 이라고 생각하기 쉽다.

int i2arr[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printf("%d\", i2arr[4]);

언뜻 생각하기엔 위와 같은 코드를 작성해서 컴파일, 실행시키면 5라는 출력값을 얻을 수 있을 것으로 생각하기 쉽지만 실제로는 이상한 값이 출력되거나 포인터 관련 에러와 함께 프로그램이 멈추는 경험을 하게 될 것이다. 이러한 문제가 생기는 이유는 n차 배열의 경우 배열 액세스 속도를 높이기 위해서 컴파일 시에 2차 이상의 배열의 시작주소를 따로 저장해 두었다가 쓰기 때문이다. 다시 말해서 컴파일러가 컴파일 시에 i2arr[0][0], i2arr[1][0], 그리고 i2arr[2][0]의 주소를 별도로 저장해 두었다가 액세스 배열의 멤버를 액세스 할 때엔 저장된 주소를 사용해서 액세스 하기 때문에, 위의 코드는 i2arr[4][0]의 위치에 해당되는 데이터를 액세스 하겠다는 의미로 컴파일러가 이해 한다. 그러므로 위와 같은 의미를 갖는 동작을 하는 코드를 작성하고 싶다면 아래와 같이 해야 한다:

 printf("%d\n", i2arr[0][4]);
위 코드로 바꾸어 프로그램을 실행시키면 5라는 값을 출력하는 것을 볼 수 가 있을 것 이다. 그러나 이런 코드는 컴파일러의 원리를 이해하는 용도로 한 번쯤 실험해 볼 가치는 있을 수 있으나 실제 프로그램을 작성할 때 이렇게 코드를 만드는 것은 프로그램 코드를 읽기 어렵게 만들 뿐 이므로 가능하면 사용하지 않는 것이 프로그램의 가독성을 향상시키는데 도움이 될 것 이다.

아래 코드의 경우 i2arr[2][0], i2arr[2][1], 그리고 i2arr[2][2]가 명시적으로 초기화 되지 않았으므로, 세 배열 멤버는 모두 0으로 초기화 될 것이다.

int i2arr[3][3] = { {1, 2, 3}, {4, 5, 6} };

참고로 아래 코드는 배열멤버 모두를 0으로 초기화 하는 효과를 갖는다.

int i2arr[3][3] = { 0 };


문자 배열[+/-]

문자배열이란, 당연한 이야기 이겠지만, '문자형 데이터(char)의 배열 '이다. 기본적으로 char 타입은 부호있는 8비트 정수형 데이터로, 숫자와 영어 대소문자, 그리고 구두점 등의 기호들만 표현이 가능하다. 한글이 안되는 프로그램을 자주 볼 수 있는 이유가 바로 이 때문이다. 한글은 부호 없는 16비트의 데이터가 필요한데, 기본으로 제공되는 데이터가 부호 있는 8비트, 결국 7비트 데이터 이기 때문에 처리가 안되는 것 이다. 인터넷을 통해 메일을 보내거나 할 때도 한글로 보낸 이메일 제목이나 본문이 제대로 전달되지 않는 이유역시 이것과 관련이 깊다.[3]

문자배열은 8비트 정수 배열의 특성도 가지고 있지만 문자열을 저장하기 위한 용도로 사용된다. 이해를 돕기 위해 문자열 상수 이야기를 먼저 하고 시작하겠다. 아래 코드는 워낙 많이 봤기 때문에 익숙 할 것 이다:

printf("Hello World!\n");

위 코드에서 "Hello World!\n" 부분이 바로 문자열 상수 이다. 문자열상수는 큰 따옴표(")로 둘러싸인 문자들을 말한다. 문자형 데이터를 설명하면서도 언급 했지만, 최근에 만들어진 컴퓨터 언어들은 큰 따옴표와 작은 따옴표(')를 구분없이 사용하는 언어들이 많지만 C 언어에서 큰 따옴표와 작은 따옴표는 그 의미가 다르다. 다음은 문자열 상수를 문자 배열에 할당하는 코드 이다.

#include <stdio.h>
int main (int argc, char *argv[])
{
    char str1[20] = "Hello World!";
    char str2[]   = "Hello Another World!";
    char *str3    = "Hello The Other World!";

    printf("Str1: %s\n", str1);
    printf("Str2: %s\n", str2);
    printf("Str3: %s\n", str3);

    return 0;
}

위 프로그램에서 세개의 문자배열에 각각의 값들이 초기화 되어 저장되고 그 문자열을 화면에 출력하게 되는데 결과적으로는 4, 5, 6 세 라인의 의미가 같은 것 처럼 보이지만 실제 내부적인 동작방식은 세 라인이 전혀 다르며, 세 라인의 가장 중요한 차이점은 프로그램이 실행될 때 할당되는 메모리의 양이 다르다는 것이다.


먼저 기억해 두어야 할 것은 str1, str2, str3 세 변수는 자동(auto) 변수이다. 즉 세 변수는 main함수가 실행될 때 스택영역이나 레지스터에 지정된 문자열이 저장될 메모리가 할당되고 main함수가 종료될 때 제거 된다. 실제로 str1 과 str2는 배열의 이름이므로 레지스터를 사용할 수 없겠지만 str3는 포인터 변수 이므로 레지스터에 여유가 있다면 레지스터에 할당되어 사용될 수 도 있을 것이다.[4] 이 것은 세 변수 공히 동일하게 동작하지만 스택에 문자배열을 저장하기 위해 할당하는 메모리 양의 차이가 발생한다. str1 변수는 문자열의 실제 길이와 상관 없이 20바이트가 할당되고 그중 앞부분 13바이트에 문자열이 할당되고 나머지 7바이트는 비워둘 것이다.[5] 그 다음, str2는 컴파일러가 문자열의 길이를 세서 메모리를 할당하기 때문에 모두 21바이트가 스택 영역에 할당될 것이다. 마지막으로 str3는 문자 배열의 이름이 아니라 포인터 타입의 변수 이므로 실제 스택영역에 할당되는 메모리는 문자열의 길이와 상관없이 포인터 타입의 변수 크기만큼의 공간을 할당하고, 스태틱 영역에 저장되어 있는 문자열의 시작 주소를 str3변수의 값으로 할당 하게 된다.

주의해야 할 것은 C에는 ‘문자열’이라는 변수 타입은 없다는 것 이다. 문자열 이라는 것은 결국 n개의 문자 데이터를 저장할 수 있는 메모리 변수를 의미하는 것 이기 때문에 한계가 분명해야 하는 C 데이터의 특성상 ‘문자열’은 그대로 다룰 수 없는 형태의 변수이다. 그 한 례로 다음의 프로그램은 에러가 발생하게 된다.

#include <stdio.h>
int main (int argc, char *argv[])
{
    char str1[20] = "Hello World!";
    printf("Before: %s\n", str1);

    str1 = "Another World!";  /* 컴파일 시에 여기에서 에러가 발생합니다. */
    printf("After: %s\n", str1);

    return 0;
}

위 프로그램을 컴파일 하려고 시도하면 7번 라인에서 컴파일 에러가 발생하게 된다. 컴파일러의 종류에 따라 에러 메시지가 다를 수 있겠지만 그 의미는 변수에 값을 넣을 수 없다는 의미이다.[6] 위 프로그램에서 str1은 스택영역내의 20바이트에 해당되는 메모리의 시작 위치를 표시하는 역할만 하는 것으로 실제 데이터를 저장할 수 있는 공간을 가지고 있지 않다.

상상을 한 번 해보자. 집 스무 채를 짓고 작은 마을을 하나 만들었다고 가정하자. 그리고 그 마을을 '앗싸리'라 명명한다면, 앗싸리에는 집이 20채 있으니 우편 배달을 하시는 분의 편의를 위해 1번지, 2번지... 20번지 라는 식으로 각각의 집에 번호를 붙여주게 될 것이다. 그럼 누군가 앗싸리에 이사를 한다는 말을 했다면 그 의미는 '앗싸리에 있는 집을 한채 얻어서 그 집으로 들어간다'라는 의미가 된다. 이제 이삿짐 센터에 전화를 해서 앗싸리로 이사해 들어가 보자, 이삿짐 센터에 전화를 해서 '앗싸리로 우리집좀 이사해 주세요'라고 한다면 이삿짐 센터에서는 무어라 하겠는가? 당연히 '앗싸리 몇번지 인가요?'라고 물어볼 것이다.

위의 프로그램에서 str1이라는 문자열 배열의 이름은 '앗싸리'라는 마을의 이름과 동급이다. 그렇기 때문에 7번줄과 같이 프로그램 코드를 작성하는 것은 구체적인 번지를 이야기 해주지 않고 무작정 '앗싸리'로 이사해 달라고 말하는 것과 같은 의미가 되기 때문에 정확한 정의를 필요로 하는 컴파일러로서는 할 수 없는 일을 요청받았다는 에러 메시지를 내보내고 컴파일을 중단 할 수 밖에 없게 되는 것이다.

배열의 개념에 대해 좀더 깊은 이해를 가지고 있는 사람이라면 'str1 배열의 첫번 위치부터 문자를 하나씩 옮겨 넣어 달라는 의미'아니냐는 의미론적인 - 실제로 최근의 대다수 프로그래밍 언어는 그렇게 받아들이는 의미로 이해되지 않는가'라는 질문을 할 수 있을 것이다. 그러나 그런 의미론적인 기술은 C언어에 비해 한참 나중에 만들어진 상대적으로 최신의 기술에 해당된다. C라는 언어는 인간의 의미론적인 접근 방법 보다는 기계적인 동작의 관점에서 접근하기 때문에 그렇게 접근할 수 없다. 이 관점은 C라는 언어를 사용하는데 있어서 전반적으로 공통된 것 이므로 아주 중요하다.

아래 프로그램은 7번 라인의 동작을 수행해주는 strcpy(3)이나 memcpy(3) 함수를 사용하지 않고 7번 라인의 동작을 수행하는 코드 이다.

#include <stdio.h>
int main (int argc, char *argv[])
{
    char str1[20] = "Hello World!";
    printf("Before: %s\n", str1);

    str1[0] = 'A';
    str1[1] = 'n';
    str1[2] = 'o';
    str1[3] = 't';
    str1[4] = 'h';
    str1[5] = 'e';
    str1[6] = 'r';
    str1[7] = ' ';
    str1[8] = 'W';
    str1[9] = 'o';
    str1[10] = 'r';
    str1[11] = 'l';
    str1[12] = 'd';
    str1[13] = '!';
    str1[14] = '\0';
    printf("After: %s\n", str1);

    return 0;
}

프로그램이 조금 어이없어 보일지 모르겠지만, C프로그래밍을 하면서 프로그램의 수행속도 향상등의 이유로 위와 같은 코드를 작성하는 경우가 가끔 있다. 어찌 되었든 한 단위 이상의 메모리 공간에 값을 넣기 위해서는 위와 같이 반복작업을 수행해야 한다.

주석 및 참고 자료[+/-]

  1. 개인적인 관점에서 볼때 100바이트가 넘는 크기라면 함수 내에서 배열을 사용하는 것은 대부분의 경우 그다지 좋은 선택은 아닙니다.
  2. 배열 자체의 크기나 차원에 대한 제한은 스펙상에 정의되어 있지 않습니다. 다시 말해서 C 언어 자체는 그러한 제한을 두고 있지 않습니다. 그러나 컴파일러의 구현이나 시스템의 한계등에 의해 어쩔 수 없이 제한 될 수 밖에 없습니다. 배열의 차원을 제한하는 컴파일러를 경험해 본 적은 없으나, 특수한 상황에서 제한할 가능성이 있습니다. 또한 배열의 전체 크기가 제한되는 경우는
    1. CPU의 특성 - 가장 대표적인 예로는 80286 CPU입니다. 어드레스 버스의 특성 때문에 배열의 최대 크기가 64k로 제한 되는 경우가 있었습니다.
    2. 메모리의 전체 크기
    3. 로컬 변수인 경우 스택의 크기
    등이 있습니다.
  3. 주: 실상은 한글이 안되는 이유는 많은 가능성이 복합적으로 작용하기 때문입니다. 이렇게 단순하게 말하면 어떤 사실에 대해 고지식 하게 접근하는 사람들은 할 말이 아주 많겠지만, 이해를 돕는다는 허울을 뒤집어 쓰고 여러 이유중 한 가지만 기술 하였습니다.
  4. 좀더 엄밀하게 따진다면 배열의 시작위치가 main함수 시작시에 레지스터에 할당되어 사용될 수 도 있을 것 입니다.
  5. 문자열 상수에 포함되어 있는 문자의 개수는 공백문자와 느낌표를 포함하여 12개의 문자인데도 13바이트라 한 것은 문자열 상수의 제일 마지막에는 항상 문자열의 끝임을 표시하는 널문자 ‘\0’가 포함되어 있기 때문에 이 문자를 포함 하였기 때문 입니다.
  6. str1 배열에 문자열을 넣는 작업을 하려면 strcpy(3)이나 memcpy(3)등의 함수를 이용하여 문자열 상수에 있는 각각의 문자 값을 배열의 대응위치로 복사하는 작업을 수행하여야 합니다. 여기에서 언급된 두 함수는 프로그래밍/C/기초_프로그래밍에서 다룰 것 입니다.