1. 서론
C 언어는 시스템 개발 및 임베디드 개발 현장에서 아직도 널리 사용되는 언어입니다. 그 중에서도 “char 배열”은 문자열을 다루기 위한 가장 기본적이면서 중요한 구문 요소 중 하나입니다。
C 언어에는 문자열 타입이 표준으로 존재하지 않습니다. 대신에、문자 배열(char 배열)을 사용하여 문자열을 표현합니다. 이는 초보자에게 매우 직관적이라고 말하기 어려워、char 배열에 대한 이해가 C 언어 학습에서 큰 장벽이 되는 경우가 종종 있습니다。
또한、char 배열과 char 포인터(char*
)의 구분 사용이나、널 문자(\0
)의 존재 등 세부적인 사양을 이해하지 못하면 예상치 못한 버그의 원인이 됩니다。
이 기사에서는、「C 언어 char 배열」이라는 주제에 초점을 맞추어、기본적인 사용법부터 응용 기술、그리고 흔히 발생하는 오류 회피 방법까지를 이해하기 쉽게 설명합니다。
앞으로 C 언어를 본격적으로 배우려는 분이나、char 배열에 대해 복습하고 싶은 분은 꼭 마지막까지 확인해 주세요。다음 장에서는 먼저、char 배열의 정의와 기본적인 구조에 대해 설명합니다。
2. char 배열이란?
C 언어에서 ‘char 배열’이란、문자(char형)를 여러 개 모아 저장하기 위한 배열입니다。이는 문자열을 다루는 데 기본이 되는 구조입니다。
char형이란?
C 언어에서는、char
형은 1문자를 표현하기 위한 데이터 타입입니다。예를 들어, 다음과 같이 1문자 변수를 정의할 수 있습니다。
char c = 'A';
이와 같이、'A'
와 같은 싱글쿼트로 둘러싸인 1문자는、char
형으로 정의됩니다。
char 배열의 기본 구문
여러 문자를 저장하려면、char
형 배열을 사용합니다。아래와 같이 정의합니다:
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
이 배열은、"Hello"
라는 5문자 문자열에 널 문자(\0
)를 추가한、6문자 분의 메모리 영역을 확보하고 있습니다。
널 문자('배열의 크기와 주의점
'
)의 중요성
배열의 크기와 주의점
'C 언어에서는、문자열의 끝을 나타내기 위해널 문자 '\0'
를 사용합니다。この 기호가 없으면、문자열 처리 함수가 올바르게 동작하지 않아 예상치 못한 동작이 발생할 가능성이 있습니다。
char str[] = "Hello"; // 자동으로 끝에 '\0' 가 추가됩니다
위와 같이、더블쿼트로 둘러싼 문자열 리터럴를 사용하면、컴파일러가 자동으로 '\0'
를 끝에 추가해 줍니다。
3. char 배열 선언 및 초기화
char 배열을 사용할 때는、필요한 문자 수 + 1(널 문자용) 크기를 확보하는 것이 기본입니다。크기가 부족한 배열에 문자열을 대입하면、버퍼 오버플로우의 원인이 되어 프로그램이 비정상 종료될 가능성도 있습니다。
정적 선언 및 초기화
char 배열을 사용하려면 먼저 적절한 선언과 초기화가 필요합니다. 여기서는 C 언어에서 char 배열의 기본적인 선언 방법부터 초기화 방법, 그리고 동적 메모리의 이용에 대해서도 설명합니다.
문자열 리터럴에 의한 초기화
가장 기본적인 방법으로는 배열 크기를 명시하여 선언하고, 1문자씩 초기화하는 방법이 있습니다.
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
이와 같이 하면, str
은 "Hello"
라는 문자열로 취급할 수 있습니다. 중요한 것은, 끝에 반드시 '\0'
를 포함해야 한다는 점입니다.
크기를 지정하여 리터럴을 대입
C 언어에서는 다음과 같이 문자열 리터럴을 사용한 간단한 초기화도 가능합니다.
char str[] = "Hello";
이 경우, 배열의 크기는 자동으로 "Hello"
+ '\0'
의 6문자 만큼이 됩니다. 문자열 내용을 변경하고 싶다면, char 배열로 선언해 두면 안전하게 수정할 수 있습니다.
동적 메모리 확보(malloc) 사용
배열 크기를 지정하면서 문자열 리터럴로 초기화하는 것도 가능하지만, 크기 부족에는 주의가 필요합니다.
char str[5] = "Hello"; // ❌ 에러의 원인(크기 부족)
위와 같이, "Hello"
는 5문자+1문자(‘\0’)의 총 6문자가 필요합니다. 따라서 최소한 char str[6]
로 해야 합니다.
4. 문자열 조작
보다 유연하게 문자열을 다루고 싶다면, malloc
을 사용하여 동적으로 char 배열을 확보하는 방법이 있습니다.
#include
#include
char* str = malloc(6 * sizeof(char));
strcpy(str, "Hello");
이 방법은 메모리를 동적으로 확보하고, 그 포인터를 통해 문자열을 다룹니다. 사용 후에는, free(str);
를 사용하여 메모리를 해제하는 것을 잊지 말아야 합니다.
문자열 복사: strcpy
C 언어에서는 char 배열
을 이용한 문자열 조작에서 표준 라이브러리 함수를 활용하는 것이 일반적입니다. 여기서는 기본적인 문자열 조작 함수와 그 사용 방법에 대해 구체적인 예와 함께 설명합니다。
문자열 결합: strcat
strcpy
는 하나의 문자열을 다른 char 배열에 복사하는 함수입니다。
#include
char src[] = "Hello";
char dest[10];
strcpy(dest, src);
주의점: dest
에 충분한 크기가 확보되지 않으면 버퍼 오버플로우의 원인이 됩니다。크기는 복사할 문자열 길이+1(널 문자)을 확보하도록 합니다。
문자열 길이 얻기: strlen
strcat
는 두 문자열을 결합합니다。두 번째 인자의 내용을 첫 번째 인자의 끝에 추가합니다。
#include
char str1[20] = "Hello";
char str2[] = " World";
strcat(str1, str2);
이 결과, str1
은 "Hello World"
가 됩니다。또한 첫 번째 인자의 배열에 결합 후 전체 크기가 들어갈 여유가 있는 것이 전제입니다。
문자열 비교: strcmp
strlen
함수는 문자열의 길이(널 문자를 제외한 문자 수)를 반환합니다。
#include
char str[] = "Hello";
int len = strlen(str); // len = 5
널 문자는 카운트되지 않으므로 C 언어에 익숙하지 않은 사람은 주의가 필요합니다。
문자열 검색: strchr
와 strstr
두 문자열이 같은지 비교하려면 strcmp
를 사용합니다。
#include
char str1[] = "Hello";
char str2[] = "World";
if (strcmp(str1, str2) == 0) {
// 같음
} else {
// 다름
}
이 함수는 같을 경우 0을 반환하고 다를 경우 문자 코드의 차이를 반환합니다。
5. char 배열과 포인터의 차이
특정 문자나 부분 문자열을 검색하려면 아래와 같은 함수를 사용할 수 있습니다。
#include
char str[] = "Hello World";
// 문자 검색
char *ptr1 = strchr(str, 'o'); // 첫 번째 'o'에 대한 포인터
// 부분 문자열 검색
char *ptr2 = strstr(str, "World"); // "World"의 시작 위치에 대한 포인터
찾지 못한 경우 두 함수 모두 NULL
를 반환합니다。
선언의 차이
C 언어에서 문자열을 다룰 때、char 배열
와 char 포인터(char*)
는 비슷해 보이지만, 실제로는 다른 성질을 가지고 있습니다.이 차이를 올바르게 이해함으로써、메모리 오용이나 예기치 않은 버그를 방지할 수 있습니다。
수정 가능성의 차이
먼저 선언 방법의 차이를 살펴보겠습니다。
char str1[] = "Hello"; // char 배열
char *str2 = "Hello"; // char 포인터
str1
는 실체를 가진 배열이며, 메모리 상에 6바이트(”Hello” + ‘ ‘)가 확보됩니다。한편、str2
는 문자열 리터럴이 저장되어 있는 메모리 영역에 대한 포인터입니다。
메모리 구조의 차이
char 배열인 str1
는、배열 내의 문자를 자유롭게 변경할 수 있습니다。
str1[0] = 'h'; // OK
하지만、char* str2 = "Hello";
와 같이 문자열 리터럴에 포인터로 접근하는 경우、그 내용을 변경하는 것은 정의되지 않은 동작입니다。
str2[0] = 'h'; // ❌ 정의되지 않은 동작(실행 시 오류 가능성)
크기 획득에 관한 차이
char 배열
스택에char 포인터
상수 영역(읽기 전용)힙 영역(malloc 등)메모리 처리에 주의가 필요
정리: 구분 사용 포인트
배열인 경우、sizeof(str1)
는 배열의 전체 바이트 수를 반환합니다。
char str1[] = "Hello";
printf("%lu", sizeof(str1)); // → 6(' ' 포함)
한편、포인터에서는 sizeof(str2)
는 포인터 자체의 크기(보통4-8바이트)를 반환하므로、배열 크기를 얻는 목적에는 사용할 수 없습니다。
char *str2 = "Hello";
printf("%lu", sizeof(str2)); // → 8(64bit 환경)
6. 함수에 char 배열을 전달하는 방법
항목 | char 배열 | char 포인터 |
---|---|---|
내용 변경 | 가능 | 원칙적으로 불가 (리터럴인 경우) |
크기 가져오기 | sizeof() | strlen() |
수정 용도 | 잘한다 | 적합하지 않음 (읽기 전용) |
유연성 | 고정 크기 | 유연하지만 주의가 필요합니다 |
기본 예제: char 배열을 인자로 전달하기
C 언어에서는 배열을 함수에 전달할 때 “값 전달”이 아니라 “포인터 전달”이 이루어집니다. 이는 char 배열
도 마찬가지이며, 함수에 전달할 때는 배열의 시작 주소(포인터)가 전달됩니다。
이 메커니즘을 이해하는 것은 함수 간에 문자열을 조작하거나 내용을 변경하는 상황에서 매우 중요합니다。
내용을 바꾸는 함수
#include
void printString(char str[]) {
printf("문자열: %s
", str);
}
int main() {
char greeting[] = "Hello";
printString(greeting);
return 0;
}
이 예제에서는 printString
함수에 char[]
형식의 인자를 전달하고 있지만, 실제로는 char*
로 받아들여집니다。즉, char str[]
는 char *str
와 동일한 의미입니다。
배열 크기 관리
함수 내부에서 배열의 내용을 바꾸는 경우에도, 전달된 주소를 통해 직접 데이터를 조작할 수 있습니다。
#include
void toUpperCase(char str[]) {
for (int i = 0; str[i] != ' '; i++) {
if ('a' <= str[i] && str[i] <= 'z') {
str[i] = str[i] - ('a' - 'A');
}
}
}
int main() {
char text[] = "hello world";
toUpperCase(text);
printf("%s
", text); // 출력: HELLO WORLD
return 0;
}
이와 같이, 배열의 내용을 변경하고 싶을 때도 포인터로 취급되기 때문에, 함수 내부의 조작이 호출자에게 반영됩니다。
const를 사용한 읽기 전용 인자
C 언어에서는 함수에 배열을 전달할 때 크기 정보가 함께 전달되지 않습니다。따라서 안전하게 조작하려면 크기도 인자로 전달하는 것이 베스트 프랙티스입니다。
void printChars(char str[], int size) {
for (int i = 0; i < size; i++) {
printf("%c ", str[i]);
}
printf("
");
}
또한, strlen
함수를 사용하여 문자열 길이를 동적으로 얻는 것도 일반적입니다. 다만, 널 종료되지 않은 배열에는 사용하지 않도록 주의하세요。
7. 실전 예제: 문자열을 역순으로 표시
문자열을 함수 내부에서 변경하지 않을 경우에는, const char*
를 사용하여 읽기 전용임을 명시하는 것이 바람직한 작성 방식입니다。
void printConstString(const char *str) {
printf("%s
", str);
}
이는 의도하지 않은 변경을 방지하고, 함수의 사양을 명확히 전달하는 데에도 이어집니다。
목적
여기서는, 지금까지 배운 char 배열
의 지식을 활용하여, 문자열을 역순으로 표시하는 프로그램을 실제로 만들어 보겠습니다.
샘플 코드
사용자가 입력한 문자열을, 끝에서 앞쪽으로 한 글자씩 출력합니다. 이는, 배열 조작 및 문자열 길이 얻기, 루프 처리 연습으로서 매우 유효합니다.
설명
#include
#include
void printReversed(char str[]) {
int len = strlen(str);
for (int i = len - 1; i >= 0; i--) {
putchar(str[i]);
}
putchar('\n');
}
int main() {
char text[100];
printf("문자열을 입력해주세요:");
fgets(text, sizeof(text), stdin);
// 줄바꿈 문자를 제거 (fgets 사용 시 대책)
size_t len = strlen(text);
if (len > 0 && text[len - 1] == '\n') {
text[len - 1] = '\0';
}
printf("역순으로 표시하면:");
printReversed(text);
return 0;
}
실행 예시
fgets
- 입력의 끝에 추가되는 (줄바꿈 문자)를 제거하는 처리도 잊지 말고 수행합니다.
strlen()
응용 팁
문자열을 입력해주세요: OpenAI
역순으로 표시하면: IAnepO
8. 자주 발생하는 오류와 그 대처법
이 처리은, 문자열의 회문 판정이나 스택 구조 이해 등, 알고리즘을 배우 데 있어 응용으로도 연결됩니다. 또한, 포인터 연산을 이용한 변형도 가능하므로, 보다 고급 연습 자료가 됩니다.
1. 널 종단(2. 버퍼 크기 부족
)의 누락
2. 버퍼 크기 부족
C언어에서 char 배열
을 다룰 때, 초보자부터 고급자까지 빠지기 쉬운 함정이 몇 가지 존재합니다. 여기에서는, 자주 발생하는 오류와 그 방지책·해결책을 구체적으로 설명합니다.
3. 문자열 리터럴의 수정
가장 흔한 실수 중 하나는 문자열의 끝에 널 문자(\0
)를 붙이는 것을 잊는 것입니다.
char str[5] = {'H', 'e', 'l', 'l', 'o'}; // ❌ 끝에 '\0' 없음
printf("%s\n", str); // 정의되지 않은 동작
대처법:
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // ✅ 올바름
또는, 문자열 리터럴을 사용함으로써 자동으로 널 문자가 추가됩니다.
4. fgets의 개행 문자 처리 누락
strcpy
와 strcat
와 같은 함수에서, 복사 대상 배열 크기가 부족하고, 메모리 파괴(버퍼 오버플로)를 일으킵니다.
char str1[5];
strcpy(str1, "Hello"); // ❌ 배열 크기 5에 6 문자 복사
대처법:
- 복사대상에는하십시오(예:「문자 수 + 1」)
- 더 안전한 사용도 고려해 보세요
char str1[6];
strncpy(str1, "Hello", sizeof(str1) - 1);
str1[5] = '\0'; // 안정을 위해 끝에 널 문자를 명시
5. 포인터와 배열의 혼동
char *str = "Hello";
와 같이 선언된 포인터는, 수정 불가능한 영역을 가리키고 있을 가능성이 있습니다. 여기에 쓰기를 하면, 실행 시 오류가 발생합니다.
char *str = "Hello";
str[0] = 'h'; // ❌ 실행 시 Segmentation fault
대처법:
char str[] = "Hello"; // ✅ 수정 가능한 배열로 선언
str[0] = 'h';
9. 요약
fgets
를 사용해 문자열을 얻은 경우, 끝에 개행 문자(\n
)가 남는 점에 주의가 필요합니다.
fgets(str, sizeof(str), stdin);
// str에는 "Hello\n\0"와 같은 내용이 들어간다
대처법:
size_t len = strlen(str);
if (len > 0 && str[len - 1] == '\n') {
str[len - 1] = '\0';
}
이 기사에서 배운 내용
보기가 비슷하기 때문에, char*
와 char[]
를 혼동해서 다루면, 정의되지 않은 동작으로 이어지는 경우도 있습니다. 크기 획득 (sizeof
)의 차이와 수정 가능 여부의 차이를 인식할 필요가 있습니다.
앞으로의 학습에 대한 조언
이 기사에서는 “C 언어의 char 배열”에 대해 기본부터 응용까지 단계적으로 설명했습니다. 마지막으로, 기사 내용을 되돌아보며 앞으로의 학습 지침을 소개합니다。
이 기사에서 배운 내용
- char 배열의 역할과 선언 방법
- 문자열 리터럴과 널 문자(
\0
)의 중요성 - 표준 라이브러리 함수를 이용한 문자열 조작
strcpy
strcat
strlen
strcmp
- char 배열과 char 포인터의 차이
- 함수에 배열을 전달하는 방법과 안전성에 대한 고려
- 실전 예시와 자주 발생하는 오류를 통해 이해를 정착시키다
앞으로의 학습에 대한 조언
char 배열의 사용법을 이해하는 것은 C 언어에서 메모리 조작의 기초를 이해하는 첫걸음입니다. 여기서 배운 지식은 다음과 같은 다음 단계에 활용될 수 있습니다。
- 포인터 연산
- 동적 메모리 관리
malloc
free
- 구조체와의 조합
또한, 다른 사람이 작성한 코드를 읽음으로써 자신에게 없던 시각과 작성 방식을 흡수할 수 있습니다. 실제 프로젝트나 OSS(오픈소스 소프트웨어)를 읽는 습관도 매우 유용합니다。
C 언어는 자유도가 높은 만큼, 올바르게 다루지 않으면 위험한 측면도 있지만, 기초를 꼼꼼히 쌓으면 매우 강력한 무기가 됩니다. char 배열의 이해는 그 첫걸음입니다. 꼭 이 기사를 참고하면서 꾸준히 실력을 높여 나가세요。