C언어 문자열 포인터 (char *)
선수학습(2개)
요약
C언어 문자열 포인터의 핵심 개념을 알아봅니다. char *와 char []의 차이, 문자열 리터럴과 포인터의 관계, %s와 %c 출력 차이를 정보처리기사 실기 대비 핵심 내용으로 정리합니다.
문자열 포인터 핵심 정리
| 개념 | 설명 | 예시 |
|---|---|---|
| 문자열 포인터 | 문자열의 시작 주소를 저장하는 포인터 | char *p = "ABC"; |
| 문자열 리터럴 | 읽기 전용 메모리에 저장된 문자열 | "HELLO" |
char * vs char [] | 포인터 vs 배열, 수정 가능 여부가 다름 | 아래 설명 참조 |
문자열 포인터란?
포인터(메모리 주소를 저장하는 변수)와 문자열('\0'으로 끝나는 char 배열), 배열을 먼저 읽고 오면 이해가 쉽습니다.
문자열 포인터는 문자열의 시작 주소를 저장하는 포인터입니다.
문자열 리터럴 "HELLO"는 메모리에 저장된 char 배열이고, 그 시작 주소가 포인터 p에 저장됩니다.
왜 char *이지 str *이 아닌가?
char는 character(문자), str은 string(문자열)의 약자입니다. C에는 str 타입이 없고 문자열 타입 자체가 존재하지 않습니다. char *의 공식 명칭은 "pointer to char" (문자 포인터)이고, "문자열 포인터"는 char *이 널 종료 문자열을 가리킬 때 편의상 부르는 이름입니다. 시험에서는 용어 자체를 묻지 않고, char *로 문자열을 다루는 코드의 출력 결과를 묻습니다.
타입은 둘 다 char *로 동일합니다. p 자체는 자기가 문자열을 가리키는지, 문자 하나를 가리키는지 모릅니다. \0까지 읽는 건 printf의 %s나 strlen 같은 함수들이 하는 일이지, 포인터가 아는 게 아닙니다.
*p→'K'한 글자 — 포인터 본연의 동작 (문자 하나를 가리킴)%s로p를 넘기면 →"KOREA"— 함수가\0까지 읽어주는 것
char *이라는 이름 자체가 "문자 하나를 가리킨다"는 의미이고, 문자열은 그 위에 \0 규약으로 만들어진 개념입니다.
char *와 char []의 차이
두 선언 모두 "HELLO"라는 문자열을 다루지만, 동작 방식이 다릅니다.
| 구분 | char *p = "HELLO" | char arr[] = "HELLO" |
|---|---|---|
| 저장 방식 | p는 문자열 리터럴의 주소를 저장 | arr에 문자열을 복사하여 저장 |
| 수정 가능 여부 | 수정 불가 (읽기 전용) | 수정 가능 |
| 다른 문자열 가리키기 | 가능 (p = "BYE") | 불가능 |
![char *p와 char arr[]의 메모리 저장 방식 차이](/_next/image?url=%2Fcoding%2Fc-language%2Fstring-pointer%2Fchar-pointer-vs-array.webp&w=1920&q=75&dpl=dpl_CQC2d89Hfyz2Sfw7aHJ6i6HVcFC1)
문자열 포인터로 값 읽기
배열 인덱싱과 포인터 역참조
문자열 포인터에서 특정 위치의 문자를 읽는 방법은 두 가지입니다.
p[i]와 *(p + i)는 완전히 동일합니다.
%s와 %c의 차이
printf에서 문자열 포인터를 출력할 때, 형식 지정자에 따라 결과가 달라집니다.
%s — 해당 주소부터 널 문자까지 출력
%c — 역참조한 문자 값 하나를 출력
| 코드 | 형식 | 의미 | 출력 |
|---|---|---|---|
printf("%s", p) | 문자열 | p 주소부터 '\0'까지 | KOREA |
printf("%s", p + 3) | 문자열 | p+3 주소부터 '\0'까지 | EA |
printf("%c", *p) | 문자 | p가 가리키는 한 문자 | K |
printf("%c", *(p + 3)) | 문자 | p+3이 가리키는 한 문자 | E |
%s와 %c는 단순히 "여러 글자 vs 한 글자"의 차이가 아닙니다. printf에 전달되는 인자의 타입 자체가 다릅니다.
char *p = "KOREA";에서 p가 주소 100을 가리킨다고 가정하면:
| 서식 지정자 | 받는 타입 | 전달 예시 | 전달되는 실제 값 |
|---|---|---|---|
%s | char* (주소) | p + 3 | 주소 103 (포인터) |
%c | char (문자 값) | *(p + 3) | 문자 'E' (ASCII 69) |
p + 3과 *(p + 3)은 같은 위치(인덱스 3)를 참조하지만, p + 3은 주소이고 *(p + 3)은 그 주소에 저장된 문자 값입니다. 서식 지정자가 달라서 출력이 다른 게 아니라, 전달하는 인자 자체가 다르기 때문에 서식 지정자도 달라야 합니다.
%s가 주소를 받는 이유
%c는 문자 한 개를 출력합니다. char는 1바이트짜리 값이니까 그 값을 그냥 넘기면 됩니다.
%s는 문자 여러 개를 출력해야 합니다. 그런데 C에서 배열(문자열 포함)은 함수에 값 자체를 전달할 수 없고, 항상 주소로만 전달됩니다. %s가 주소를 받는 건 임의로 정한 규칙이 아니라, 문자열을 전달하는 유일한 방법이 주소뿐이기 때문입니다.
| 서식 지정자 | 출력 대상 | 전달 방식 | 이유 |
|---|---|---|---|
%c | 문자 1개 | 값을 직접 전달 | 1바이트 값이므로 그대로 넘김 |
%s | 문자열 (여러 문자) | 시작 주소를 전달 | 배열은 주소로만 전달 가능 |
서식 지정자와 인자 타입이 맞지 않으면?
C 컴파일러는 타입이 맞지 않아도 경고만 주고 컴파일을 막지 않습니다. 하지만 실행 결과는 의도와 완전히 달라집니다.
| 잘못된 코드 | 원인 | 결과 |
|---|---|---|
printf("%c", p + 3) | 주소를 문자로 해석 | 엉뚱한 문자 출력 |
printf("%s", *(p + 3)) | 문자값 69를 주소로 해석 | 프로그램 중단 (세그멘테이션 폴트) |
세그멘테이션 폴트(segmentation fault)란 접근 권한이 없는 메모리를 읽으려 할 때 운영체제가 프로그램을 강제 종료시키는 오류입니다.
문자열 리터럴이란?
문자열 리터럴은 코드에 큰따옴표("")로 직접 쓴 문자열 값을 말합니다.
문자열과 문자열 리터럴의 차이
문자열은 '\0'으로 끝나는 char 배열을 가리키는 일반적인 개념이고, 문자열 리터럴은 코드에 ""로 직접 쓴 값 자체를 말합니다.
| 구분 | 문자열 | 문자열 리터럴 |
|---|---|---|
| 의미 | '\0'으로 끝나는 char 배열 (일반 개념) | 코드에 ""로 직접 쓴 값 |
| 수정 가능 여부 | 배열이면 수정 가능 | 수정 불가 (읽기 전용) |
| 예시 | char a[] = "HI";의 a | "HI" 자체 |
문자열 리터럴의 저장 방식
코드에 "HELLO"를 쓰면, 컴파일러가 읽기 전용 메모리 영역에 문자들을 저장합니다.
이후 코드에서 "HELLO"라고 쓴 부분은 이 메모리의 시작 주소(위 예시에서 100)로 치환됩니다. 그래서 char *p = "HELLO";는 포인터 p에 이 주소를 저장하는 것이고, char arr[] = "HELLO";는 이 데이터를 배열에 복사하는 것입니다.
문자열 리터럴은 주소다
문자열 리터럴 "HELLO"는 그 자체가 메모리 주소입니다. 따라서 문자열 리터럴에 직접 포인터 산술을 사용할 수도 있습니다.
이 원리는 구조체 멤버가 const char * 타입일 때도 동일하게 적용됩니다.
t.g는 "DC"의 시작 주소이므로, t.g + 1은 두 번째 문자 'C'의 주소입니다. %s로 출력하면 "C"가 출력됩니다.
배열 이름도 포인터처럼 동작
char 배열의 이름은 첫 번째 요소의 주소입니다. 따라서 배열 이름을 char * 포인터에 저장할 수 있습니다.
| 표현 | 의미 | 값 |
|---|---|---|
a | 배열의 시작 주소 (= &a[0]) | 주소 |
p | a의 시작 주소를 저장 | 주소 |
*a | 배열의 첫 번째 문자 | 'A' |
*p | p가 가리키는 문자 | 'A' |
a[1] | 배열의 두 번째 문자 | 'r' |
p[1] | 포인터로 접근한 두 번째 문자 | 'r' |
문자열 포인터와 함수
문자열을 함수에 전달할 때 char * 매개변수를 사용합니다.
문자열 길이 계산
포인터 p를 p++로 한 칸씩 이동시키면서 널 문자를 만날 때까지 세는 방식입니다.
const char * — 읽기 전용 포인터
const가 붙으면 포인터가 가리키는 값을 수정할 수 없습니다. 원본 문자열을 보호할 때 사용합니다.
const char *s는 원본(source)을 읽기만 하고, char *d는 대상(destination)에 쓰기를 합니다. 문자열 복사에서 자세히 다룹니다.
문자열 포인터를 더 깊이 배우고 싶다면 문자열 복사와 포인터 배열도 시험에 자주 출제되니 함께 읽어보세요.