모던지 / 정보처리기사 / 프로그래밍 언어 활용 3강. 배열·포인터·문자열

프로그래밍 언어 활용 3강. 배열·포인터·문자열

포인터는 어렵기로 유명하지만, 사실 개념은 한 줄입니다. 값을 담는 대신 값이 있는 주소를 담는 변수라는 것.

풀고 시작

문제 1. C에서 배열 이름이 의미하는 것으로 옳은 것은?
배열 이름은 첫 원소를 가리키는 주소로 취급됩니다. 그래서 배열 이름을 포인터에 그대로 대입할 수 있습니다. 값 자체를 얻으려면 첨자를 붙이거나 별표로 간접 참조해야 합니다.

배열은 같은 상자를 줄지어 놓은 것입니다

배열은 같은 자료형의 값을 연속된 메모리에 나란히 두고 번호로 접근하는 구조입니다. 첨자는 0부터 시작하므로, 크기가 5인 배열의 유효한 첨자는 0부터 4까지입니다. 5를 쓰면 남의 메모리를 건드리게 되는데, C는 이것을 막아 주지 않습니다. 시험이 아니라 실무에서 사고가 나는 지점이 바로 여기입니다.

2차원 배열은 행과 열로 접근합니다. C에서는 행 우선으로 저장되어, 첫 행의 원소들이 먼저 나란히 놓이고 그다음 행이 이어집니다.

배열 초기화에도 규칙이 있습니다. 초기값을 일부만 주면 나머지는 0으로 채워집니다. 크기를 생략하고 초기값만 주면 그 개수만큼 크기가 정해집니다.

포인터는 주소를 담는 변수입니다

포인터를 이해하는 열쇠는 기호 두 개입니다. 앰퍼샌드(&)는 주소를 얻는 연산자이고, 별표는 그 주소가 가리키는 곳의 값을 꺼내는 연산자입니다. 별표는 선언할 때와 사용할 때 뜻이 다르다는 점이 초심자를 헷갈리게 하죠. 선언에서의 별표는 "이건 포인터야"라는 표시이고, 사용할 때의 별표는 "가리키는 값을 꺼내라"는 명령입니다.

int a = 10;
int *p = &a;
*p = 20;

이 코드가 끝나면 a는 20이 됩니다. p가 a의 주소를 갖고 있으니 p를 통해 a를 고친 것이죠.

포인터가 왜 필요할까요. 함수에 값을 넘기는 방식 때문입니다. 값에 의한 호출(call by value)은 값을 복사해서 넘기므로 함수 안에서 아무리 바꿔도 원본은 그대로입니다. 참조에 의한 호출(call by reference)은 주소를 넘기므로 함수 안의 변경이 원본에 반영됩니다. 두 정수를 맞바꾸는 함수를 만들 때 값을 넘기면 실패하고 주소를 넘겨야 성공하는 그 유명한 예제가 여기서 나옵니다.

배열과 포인터는 사실상 한 몸입니다. 배열 이름 자체가 첫 원소의 주소이므로, 포인터에 배열 이름을 대입하면 그 포인터로 배열 전체를 다룰 수 있습니다. 포인터에 1을 더하면 자료형의 크기만큼 주소가 움직입니다. int 포인터에 1을 더하면 주소가 4바이트 이동하는 식이죠. 이것을 포인터 연산이라고 합니다.

문자열은 널 문자로 끝납니다

C에는 문자열 타입이 따로 없습니다. 문자의 배열로 다루고, 그 끝을 널 문자로 표시합니다. 그래서 다섯 글자를 담으려면 배열 크기가 여섯이어야 합니다. 이 한 칸을 잊는 것이 문자열 관련 사고의 절반입니다.

함수 하는 일
strlen 문자열의 길이를 구한다. 널 문자는 세지 않는다
strcpy 문자열을 복사한다
strcat 문자열 뒤에 다른 문자열을 붙인다
strcmp 두 문자열을 사전 순으로 비교한다

strlen은 널 문자를 세지 않는다는 점이 문제로 나옵니다. 다섯 글자 문자열이라면 배열 크기는 여섯이지만 strlen의 결과는 5입니다.

자바와 파이썬은 문자열을 객체로 다룹니다. 자바의 String은 불변이라 값을 바꾸면 새 객체가 만들어지고, 그래서 반복해서 이어 붙일 때는 StringBuilder를 씁니다. 파이썬의 문자열도 불변이며 슬라이싱으로 부분을 잘라낼 수 있습니다.

구조체는 다른 종류를 묶습니다

배열이 같은 자료형을 묶는다면, 구조체는 서로 다른 자료형을 하나로 묶습니다. 학번은 정수, 이름은 문자 배열, 학점은 실수인 학생 정보를 한 덩어리로 다루는 식이죠. 구성 요소에 접근할 때는 점 연산자를 쓰고, 구조체 포인터로 접근할 때는 화살표 연산자를 씁니다.

공용체(union)도 나옵니다. 구조체와 문법은 비슷하지만 모든 멤버가 같은 메모리를 공유합니다. 그래서 크기는 가장 큰 멤버의 크기가 되고, 한 번에 하나의 멤버만 의미를 갖습니다.

여기까지 오면 4과목 코드 문제의 재료가 거의 다 모입니다. 다음 강에서는 객체지향 언어의 문법과 예외 처리를 봅니다.

인출 문제

문제 1. 함수에 인자를 전달할 때, 함수 안에서의 수정이 호출한 쪽의 원본 변수에 반영되는 방식은?
주소를 넘겨 원본을 직접 다루게 하는 것이 참조에 의한 호출입니다. 값에 의한 호출은 복사본을 넘기므로 원본이 바뀌지 않고, 나머지 둘은 일부 언어 이론에서 다루는 방식이라 C의 표준 동작과는 거리가 있습니다.
문제 2. C에서 다섯 글자로 이루어진 문자열을 저장하기 위해 필요한 문자 배열의 최소 크기와, strlen 함수가 반환하는 값을 순서대로 옳게 나열한 것은?
문자열의 끝을 알리는 널 문자를 위해 한 칸이 더 필요하므로 배열 크기는 6이어야 합니다. 반면 strlen은 널 문자를 세지 않고 실제 글자 수만 돌려주므로 5가 나옵니다.
문제 3. 공용체에 대한 설명으로 옳은 것은?
공용체는 하나의 공간을 여러 멤버가 나눠 쓰는 구조라 한 번에 한 멤버만 유효합니다. 멤버마다 공간을 따로 갖는 것은 구조체이고, 그래서 같은 멤버 구성이라면 공용체가 구조체보다 작거나 같습니다.
문제 4. int형 포인터 p가 주소 1000을 가리키고 있을 때, p + 1이 가리키는 주소는? (int는 4바이트로 가정)
포인터 연산은 가리키는 자료형의 크기 단위로 이동합니다. int가 4바이트이므로 1을 더하면 4바이트 뒤인 1004가 됩니다. 단순히 1이 더해진다고 보는 것이 가장 흔한 오답입니다.

생각해볼 질문 (정답 없음)

C는 배열 범위를 벗어난 접근을 막지 않습니다. 프로그래머를 믿는 설계였죠. 자유와 안전 중 언어는 어느 쪽을 기본값으로 삼아야 할까요.


이전: 2강 변수·연산자·제어문 · 다음: 4강 객체지향과 예외 처리

모던지 · 궁금하면 모던지 GitHub · 2026-09-10