정보처리기사 실기 C 배열·포인터: 값 변화와 출력 결과 추적하는 법
2026. 10. 7. 14:35ㆍWORK/커리어

이 글은 LIFE ARCHITECTURE의 WORK 카테고리 ‘일하며 공부하는 구조’ 기록입니다. 퇴근 후 짧은 시간에 자격증 공부를 이어가려면 의지보다 ‘한 번에 끝낼 수 있는 작은 단위’로 공부를 쪼개는 구조가 필요합니다. 이번에는 C 배열과 포인터를 예로, 가리키는 위치와 배열에 저장된 값을 따로 기록하며 출력 결과를 추적하는 방법을 정리했습니다.
저녁 공부 시간을 조금 남겨 두고 짧은 C 코드를 펼친다. 배열의 숫자는 단순한데 별표와 더하기 기호가 붙는 순간 눈이 멈춘다. 방금 바뀐 것은 포인터였을까, 포인터가 가리키는 값이었을까. 처음부터 머릿속에서 모두 계산하려 하면 앞줄의 변화를 놓치기 쉽다.
출력 문제를 풀 때는 배열의 현재 값과 포인터가 가리키는 인덱스를 별도로 기록하고, 한 문장씩 갱신하면 된다. 이 글은 그 방법을 세 개의 자체 연습 예제로 익힌다. 예제는 기출문제를 옮긴 것이 아니라 이번 설명을 위해 구성했다.
주소 숫자를 외우기 전에 어느 칸인지 표시한다
실제 메모리 주소를 길게 적기보다 배열 칸에 번호를 붙인다. 아래 예제의 배열은 네 칸이다. 인덱스는 0부터 시작하며 초기 내용은 차례로 3, 6, 9, 12이다. 포인터의 이동을 읽을 때 이 네 칸을 계속 기준으로 사용한다.
int a[] = {3, 6, 9, 12};
int *p = a;
int *q = a + 2;
시작 상태를 ‘p는 0번, q는 2번’이라고 적는다. p가 가리키는 내용은 3이고 q가 가리키는 내용은 9다. 포인터의 위치를 나타내는 0과 그 자리에 있는 3을 같은 기록으로 섞지 않는 것이 이번 풀이의 출발점이다.
이 예제에는 배열을 복사하는 문장이 없다. q를 선언했다고 새 배열이 만들어지지 않는다. p와 q는 같은 a 배열의 서로 다른 칸을 참고한다. 이후 q를 통해 값을 바꾸면 a의 해당 칸에 변경을 적는다. 변수 이름이 늘어날 때마다 배열을 새로 그리는 실수를 줄일 수 있다.
한 줄마다 위치 변화와 값 변화를 따로 적는다
첫 번째 전체 프로그램은 다음과 같다. 우선 실행하지 말고 마지막 두 줄의 출력을 종이에 적어 보자. 값을 바꾸는 줄과 위치를 바꾸는 줄을 다른 색으로 표시해도 좋다.
#include <stdio.h>
int main(void) {
int a[] = {3, 6, 9, 12};
int *p = a;
int *q = a + 2;
*p += 2;
p++;
*q = *p + a[0];
*(p + 2) -= 1;
printf("%d %d %d %d\n", a[0], a[1], a[2], a[3]);
printf("%d %d\n", *p, *q);
return 0;
}
첫 계산에서 a의 0번 칸은 3에서 5가 된다. 다음 줄에서는 p가 1번 칸으로 이동한다. 이때 a의 1번 칸을 7로 고치면 안 된다. p가 이동했을 뿐 그 칸의 내용은 여전히 6이다.
그다음 q의 대상인 2번 칸에는 현재 p의 내용 6과 현재 a[0]의 내용 5를 더한 11이 들어간다. 여기서 초기 a[0]의 3을 다시 가져오면 9라는 잘못된 답이 나온다. 계산할 때는 문제 처음의 배열이 아니라 직전 줄까지 갱신한 배열을 본다.
마지막 변경 줄은 현재 p에서 두 칸 뒤인 3번 칸을 찾아 12를 11로 바꾼다. 이 표현을 평가했다고 p 자체가 3번으로 이동한 것은 아니다. p의 위치 기록은 1번으로 남는다.
변화표를 채우면 마지막 출력은 읽는 일이 된다
위 네 문장을 다음과 같이 추적했다. 칸의 숫자를 한꺼번에 외우는 대신, 각 줄에서 바뀐 정보만 갱신한 결과다.
| 실행 직후 | p의 위치 | q의 위치 | a[0] | a[1] | a[2] | a[3] |
|---|---|---|---|---|---|---|
| 초기화 | 0번 | 2번 | 3 | 6 | 9 | 12 |
*p += 2; |
0번 | 2번 | 5 | 6 | 9 | 12 |
p++; |
1번 | 2번 | 5 | 6 | 9 | 12 |
*q = *p + a[0]; |
1번 | 2번 | 5 | 6 | 11 | 12 |
*(p + 2) -= 1; |
1번 | 2번 | 5 | 6 | 11 | 11 |
첫 출력은 배열 전체를 순서대로 읽는다. 두 번째 출력은 p와 q가 각각 가리키는 칸의 현재 내용을 읽는다. 따라서 결과는 다음과 같다.
5 6 11 11
6 11
작성 과정에서 이 프로그램을 GCC의 C11 모드로 컴파일하고 실행해 표의 결과와 대조했다. 컴파일 경고 없이 위 두 줄이 출력됐다. 이 실행 기록은 이번 예제의 검산이며, 운영자의 응시 경험이나 특정 시험의 출제 기록을 뜻하지 않는다.
손으로 적은 결과와 실제 출력이 다르면 답만 고치지 않는다. 표에서 최초로 달라진 줄을 찾는다. 0번 칸의 갱신을 놓쳤는지, p의 위치를 옮기는 줄에서 배열 값까지 바꿨는지 살피면 다음 문제에서 고칠 습관이 구체적으로 남는다.
별표와 증가 연산자는 괄호부터 구분한다
이제 배열을 새로 시작한다. 앞 예제의 a를 이어 쓰지 않는다. 다음 세 줄은 각각 별도의 문장이므로 한 줄의 변경이 끝난 뒤 다음 줄을 읽는다.
int b[] = {2, 4, 6};
int *r = b;
int x = *r++;
int y = (*r)++;
int z = *++r;
이 예제의 첫 줄 계산을 마치면 x는 2, r의 위치는 1번이다. 이어 y에는 1번 칸의 변경 전 내용인 4가 들어가고, b의 1번 칸은 5가 된다. 마지막 계산에서는 r이 2번으로 이동하고 z에 그 칸의 6이 들어간다.
최종 결과를 두 묶음으로 적는다. x, y, z는 ‘2 4 6’이고 b의 세 칸은 ‘2 5 6’이다. 앞 예제와 마찬가지로 실제 C 프로그램에 넣어 실행했으며 두 묶음의 결과가 일치했다.
| 이 예제의 표현 | 값을 받는 변수 | 문장 종료 후 r 위치 | 변경된 배열 내용 |
|---|---|---|---|
int x = *r++; |
x = 2 | 1번 | 없음 |
int y = (*r)++; |
y = 4 | 1번 | b[1]이 5가 됨 |
int z = *++r; |
z = 6 | 2번 | 없음 |
첫 표현은 *(r++)로 묶어서 보고 두 번째는 괄호 안의 대상을 확인한다. 괄호를 그리는 일은 별도의 장식이 아니다. 표의 어느 열을 고쳐야 하는지 정하는 과정이다. 눈에 익숙하다는 이유로 세 표현을 같은 뜻으로 읽지 않는 연습이 필요하다.
배열 범위를 벗어난 계산은 정답 숫자부터 찾지 않는다
포인터에 정수를 더한 결과는 같은 배열의 원소 또는 마지막 원소 바로 다음 위치 범위 안에서 다뤄야 한다. 배열 끝 바로 다음 위치를 만들 수 있다는 것과 그곳의 내용을 읽을 수 있다는 것은 다르다. 마지막 다음 위치를 역참조해 읽는 것은 허용되지 않는다. 이 경계는 C 위원회 공개 초안 N1570의 6.5.6절에서 확인했다.
오늘 첫 예제에서는 p가 1번인 상태에서 p + 2를 사용하므로 대상은 3번이다. a의 유효 인덱스가 0~3이라는 기록과 맞는다. 만약 같은 상태에서 p + 3을 역참조한다면 마지막 다음 위치를 읽으려는 문제가 된다. 그런 코드를 실행해 나온 우연한 숫자를 정답처럼 적어서는 안 된다.
출력을 예측하는 문제에서도 먼저 조건을 확인하는 순서를 둔다. 현재 위치, 이동량, 배열 길이를 적은 뒤 실제 대상 칸이 존재하는지 본다. 코드가 짧아도 범위 검사를 생략할 이유가 없다. 이해할 수 있는 프로그램인지 확인하는 과정과 결과를 계산하는 과정을 함께 가져간다.
작은 연습에서는 바뀐 칸을 말로 설명해 본다
다음은 직접 풀어 볼 자체 연습이다. c의 최종 배열을 먼저 적고, 각 줄이 그 결과를 만드는 이유를 한 문장씩 붙여 보자.
int c[] = {5, 10, 15};
int *s = c + 1;
*(s - 1) = *s + 3;
s++;
*s = c[0] - c[1];
처음 s는 1번 칸을 가리킨다. 첫 대입에서 s - 1의 대상은 0번이고 우변은 10 + 3이므로 c[0]은 13이 된다. s가 2번으로 이동한 뒤 그 칸에는 13 - 10인 3이 들어간다. 최종 배열은 ‘13 10 3’이다. 이 연습도 실행 결과와 맞춰 확인했다.
정답을 맞힌 뒤에는 변형 하나만 해 보자. 마지막 우변을 c[0] + c[1]로 바꾸면 2번 칸은 23이 된다. 두 번째 문제를 새로 외우기보다 같은 변화표에서 어느 계산만 달라지는지 확인한다. 변형의 목적은 풀이 과정의 한 부분을 분명하게 만드는 데 있다.
검산 전 체크리스트는 생각의 순서를 고정한다
문제마다 새로운 표를 만들 필요는 없다. 배열 칸, 포인터 위치, 출력 변수라는 세 종류의 기록만 남겨도 오늘 예제들은 풀 수 있다. 다음 목록을 마지막 출력 직전에 확인한다.
- 배열 길이와 유효 인덱스를 적었다.
- 포인터를 선언한 직후의 대상 칸을 표시했다.
- 위치 변경과 배열 내용 변경을 서로 다른 열에 기록했다.
- 우변은 갱신된 배열의 현재 값으로 계산했다.
- 별표와 증가 연산자가 붙은 표현의 괄호를 확인했다.
- 역참조하는 대상 칸이 배열 범위 안에 있는지 확인했다.
- 출력 변수와 배열의 최종 상태를 구분했다.
- 손으로 만든 표와 실행 결과가 다르면 첫 차이부터 찾았다.
하루 공부의 종료 기준도 작게 잡을 수 있다. 코드를 여러 개 훑는 대신 오늘 예제 하나의 변화표를 빈 종이에 다시 만들고, 각 줄에서 어느 칸이 바뀌는지 설명하는 데까지 해 본다. 공부 시간이 짧을수록 끝냈다는 기준이 구체적일 필요가 있다.
자주 막히는 질문은 기록의 기준으로 돌아간다
FAQ. p + 1은 주소 숫자에 1을 더한다는 뜻인가? 배열 원소를 기준으로 다음 칸을 가리키는 이동으로 읽는다. 오늘 예제에서는 int 원소 한 칸이다. 특정 환경의 주소 숫자를 가정할 필요 없이 대상 인덱스를 갱신하면 출력 풀이를 진행할 수 있다.
FAQ. 같은 칸을 가리키는 포인터가 두 개면 값도 두 개 생기는가? 오늘처럼 동일한 배열 원소를 함께 가리키는 경우 배열의 그 칸은 하나다. 어느 포인터를 통해 쓰든 같은 칸의 기록을 고친다. 이를 확인하려면 첫 예제에서 q를 a로 초기화하는 변형을 따로 만들어 표를 다시 채워 볼 수 있다.
FAQ. 컴퓨터 출력과 같으면 풀이를 끝내도 될까? 숫자가 일치한 뒤에도 이유를 확인한다. 표의 갱신 순서가 설명되지 않으면 다른 초기값에서 다시 막힐 수 있다. 또한 범위 밖 접근 등 문제가 있는 코드는 한 번의 실행 결과로 타당성을 판단하지 않는다. 오늘처럼 범위와 문장을 먼저 검토한 뒤 실행을 보조 검산으로 사용한다.
오늘은 한 줄을 읽을 때 두 가지를 적어 보자. 어느 칸을 가리키는가. 그 칸에는 지금 무엇이 들어 있는가. 포인터 공부가 복잡하게 느껴질 때 필요한 것은 더 큰 결심보다 기록을 나누는 작은 방법일 수 있다. 다음 줄로 넘어가기 전에 현재 상태를 남기는 일부터 시작한다.
자료 확인일은 2026년 10월 7일이다. Q-Net 정보처리기사 안내에서 2026.1.1~2026.12.31 적용 출제기준의 게시 여부와 시험 안내를 확인했다. 연결된 출제기준 첨부 원문의 세부 항목은 이번 접근에서 열리지 않아 인용하지 않았다. 언어 규칙은 C 위원회 공개 초안 N1570의 배열·표현식·포인터 연산 항목을 참고했다. 세 예제와 변화표는 자체 작성하고 C11 모드 실행으로 검산한 학습 자료다.
'WORK > 커리어' 카테고리의 다른 글
| 정보처리기사 실기 트랜잭션·동시성·회복 | COMMIT과 격리 수준 예제 (0) | 2026.10.14 |
|---|---|
| 정보처리기사 실기 정규화와 함수 종속 | 복합키·2정규형·3정규형 예제 (0) | 2026.10.12 |
| 정보처리기사 실기 Python 반복문·리스트 출력 결과 추적법 | 표로 푸는 예제 (0) | 2026.10.05 |
| 정보처리기사 실기 SQL JOIN과 NULL을 예제 표로 이해하기 (0) | 2026.10.04 |
| 정보처리기사 실기 SQL SELECT·WHERE·ORDER BY 결과 읽기 (0) | 2026.09.30 |