Skip to main content

Command Palette

Search for a command to run...

자료구조 (1)

Updated
5 min read

1. 배열

1-1. 배열이란?

배열은 같은 타입의 데이터를 연속된 메모리 공간에 나란히 저장하는 자료구조 다.

변수 하나에는 값 하나만 저장할 수 있다. 학생 100명의 점수를 저장해야 한다면 int score1, score2, score3, ... score100; 이렇게 변수를 100개 만들어야 할까? 비현실적이다. 배열을 쓰면 int score[100]; 한 줄로 해결된다.

JavaScript의 배열과 비슷한 개념이지만, 결정적인 차이가 있다. JavaScript 배열은 서로 다른 타입을 섞어 넣을 수 있고 크기도 자유롭게 변한다. C 배열은 하나의 타입만 저장할 수 있고, 크기가 고정 되어 있다.

1-2. 배열의 이해와 사용

배열의 선언 구조

자료형 배열이름[크기];
int scores[5];        // int 5개를 담는 배열
double temps[7];      // double 7개를 담는 배열
char name[20];        // char 20개를 담는 배열 (문자열용)

int scores[5] 를 선언하면 메모리에 int 크기(4바이트) × 5 = 20바이트의 연속된 공간이 확보된다. 각 요소는 scores[0]부터 scores[4]까지 인덱스로 접근한다. 인덱스는 0부터 시작 한다.

scores[0] = 90;
scores[1] = 85;
scores[2] = 78;
printf("%d\n", scores[1]);  // 85

배열의 속성

배열 이름은 배열 첫 번째 요소의 주소 를 가리킨다. 즉 scores&scores[0]은 같은 값이다.

int scores[5] = {90, 85, 78, 92, 88};
printf("%p\n", scores);       // 배열 시작 주소
printf("%p\n", &scores[0]);   // 같은 주소

배열의 전체 크기는 sizeof 연산자로 구할 수 있다. 요소 개수는 전체 크기를 요소 하나의 크기로 나누면 된다.

int arr[5];
printf("전체 크기: %lu\n", sizeof(arr));        // 20 (4 × 5)
printf("요소 개수: %lu\n", sizeof(arr) / sizeof(arr[0]));  // 5

다만 배열을 함수에 매개변수로 넘기면 포인터로 변환 되기 때문에, 함수 안에서 sizeof로 배열 크기를 구할 수 없다. 그래서 배열을 함수에 넘길 때는 크기도 함께 전달하는 것이 일반적이다.

void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
}

배열의 크기는 왜 상수여야 하는가?

컴파일타임과 런타임

이 개념을 이해하려면 먼저 컴파일타임(compile time)런타임(runtime) 의 차이를 알아야 한다.

컴파일타임 은 소스 코드가 기계어로 번역되는 시점이다. 컴파일러가 코드를 읽고, 문법을 검사하고, 실행 파일을 만드는 과정이 여기에 해당한다. 런타임 은 그 실행 파일이 실제로 실행되는 시점이다. 사용자 입력을 받거나, 파일을 읽거나, 계산 결과가 나오는 것은 전부 런타임에 일어난다.

쉽게 말해 컴파일타임은 "코드를 번역하는 시점", 런타임은 "프로그램이 돌아가는 시점"이다.

C에서 일반 배열(정적 배열)은 스택 메모리 에 할당된다. 스택은 함수가 호출될 때 필요한 공간을 미리 계산해서 확보하는 구조다. 이 계산은 컴파일타임 에 이루어진다.

int arr[5];  // 컴파일러가 "20바이트 필요하겠구나" 판단 가능

컴파일러는 5라는 숫자를 보고 4 × 5 = 20바이트를 확보하면 된다는 걸 바로 알 수 있다. 그런데 크기가 변수라면 어떻게 될까?

int n;
scanf("%d", &n);
int arr[n];  // n은 런타임에야 알 수 있음

n의 값은 사용자가 입력해야 알 수 있다. 컴파일 시점에서는 배열에 얼마만큼의 메모리를 잡아야 할지 결정할 수 없다. 그래서 C89/C90 표준에서는 배열 크기를 반드시 상수 로 지정해야 한다.

int arr[5];            // OK — 상수
const int SIZE = 5;
int arr2[SIZE];        // 컴파일러에 따라 다름 (C에서 const는 진정한 상수가 아님)
#define MAX 100
int arr3[MAX];         // OK — 매크로 상수는 전처리 단계에서 치환됨

C99부터는 가변 길이 배열(VLA, Variable Length Array) 이 도입되어 int arr[n] 같은 문법이 허용되기도 한다. 하지만 Visual Studio에서는 VLA를 지원하지 않으므로, 수업 환경에서는 배열 크기를 항상 상수로 지정해야 한다.

크기가 런타임에 결정되어야 하는 경우에는 malloc을 사용한 동적 메모리 할당 으로 해결한다. 이건 이후에 포인터와 함께 다루게 된다.

배열의 초기화

배열을 선언하면서 동시에 값을 넣을 수 있다.

int arr[5] = {10, 20, 30, 40, 50};

요소 수보다 적게 초기화하면 나머지는 자동으로 0 이 된다.

int arr[5] = {10, 20};  // {10, 20, 0, 0, 0}
int arr2[5] = {0};       // 전부 0으로 초기화

크기를 생략하면 초기화한 요소 수에 맞춰 자동으로 크기가 결정된다.

int arr[] = {10, 20, 30};  // 크기 3짜리 배열

초기화하지 않은 지역 배열에는 쓰레기값 이 들어있다. 전역이나 static 배열은 자동으로 0으로 초기화된다.

int globalArr[5];  // 전역 — 전부 0

void foo(void) {
    int localArr[5];         // 지역 — 쓰레기값
    static int staticArr[5]; // 정적 — 전부 0
}

1-3. 배열과 문자열

C에는 JavaScript의 string 같은 문자열 타입이 없다. 대신 char 배열 로 문자열을 표현한다.

문자열 변수

C에서 문자열은 널 문자(\0)로 끝나는 char 배열 이다. 널 문자는 문자열의 끝을 알려주는 표식이다. 모든 문자열 처리 함수(printf%s, strlen, strcpy 등)는 이 \0을 기준으로 문자열의 끝을 판단한다.

char name[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
printf("%s\n", name);  // Hello

매번 이렇게 쓰면 불편하니까, 문자열 리터럴로 간단하게 초기화할 수 있다. 이 경우 \0이 자동으로 붙는다.

char name[6] = "Hello";  // 자동으로 끝에 '\0' 추가

"Hello"는 문자 5개지만 \0까지 포함하면 6바이트가 필요하다. 배열 크기를 5로 잡으면 \0이 들어갈 자리가 없어서 문자열 함수들이 오작동 한다. 배열 크기는 항상 문자 수 + 1 이상으로 잡아야 한다.

char name[] = "Hello";  // 크기를 생략하면 자동으로 6 (5 + 널 문자)

문자 하나('A')와 문자열 하나("A")는 다르다. 'A'char 타입으로 1바이트이고, "A"'A' + '\0'으로 구성된 2바이트짜리 char 배열이다.

문자열을 입력받을 때는 scanf에서 %s를 사용한다. 이때 배열 이름 자체가 주소이므로 &를 붙이지 않는다.

char name[20];
printf("이름을 입력하세요: ");
scanf("%s", name);  // & 없이 배열 이름만
printf("안녕하세요, %s\n", name);

다만 scanf%s는 공백을 만나면 입력을 멈춘다. "Hong Gil Dong"을 입력하면 "Hong"만 저장된다. 공백을 포함한 문자열을 입력받으려면 fgets를 사용한다.

char line[100];
fgets(line, sizeof(line), stdin);

문자열 관련 주요 함수들은 <string.h>에 들어있다. strlen은 문자열 길이를 반환하고, strcpy는 문자열을 복사하고, strcmp는 두 문자열을 비교한다. C에서는 = 연산자로 문자열을 대입하거나 ==로 비교할 수 없으므로, 반드시 이 함수들을 사용해야 한다.

#include <string.h>

char src[] = "Hello";
char dest[20];

strcpy(dest, src);              // 복사
printf("%lu\n", strlen(dest));  // 5 (널 문자 제외한 길이)

if (strcmp(src, dest) == 0) {
    printf("같은 문자열\n");
}

마무리

배열은 C언어에서 데이터를 묶어서 관리하는 가장 기본적인 방법이다. 핵심은 세 가지다 — 크기가 고정이라는 것, 인덱스가 0부터 시작한다는 것, 그리고 배열 이름이 첫 번째 요소의 주소라는 것. 특히 문자열이 \0으로 끝나는 char 배열이라는 점은 C 프로그래밍에서 끊임없이 등장하는 개념이니 확실히 이해해두는 것이 좋다.

More from this blog

C언어 (13)

1. STL의 개념 1-1. 배경 C++로 프로그래밍을 하다 보면 동적 배열, 연결 리스트, 정렬, 검색 같은 자료구조와 알고리즘을 반복적으로 구현하게 된다. 프로젝트마다 매번 새로 만들면 시간도 낭비되고, 버그가 생길 가능성도 높아진다. 이런 문제를 해결하기 위해 자주 사용되는 자료구조와 알고리즘을 미리 만들어서 표준 라이브러리에 포함 시킨 것이 STL이

Apr 1, 202610 min read3

chamdom's tech

16 posts