정보처리기사 실기 2026년 3회 정답과 해설을 정리했다. 3회 실기를 직접 치고 나서, 수험생들이 복원한 20문항의 답을 하나씩 다시 확인했다. 복원 글에 적힌 답을 그대로 옮기지 않고 코드 8문항은 컴파일해 실행했고, SQL 2문항은 Oracle에서 돌렸고, 서브넷은 계산으로 확인했다(실행 검증은 Claude Code로 했다). 전부 복원 정답과 같았다.
2026-10-26 기준이다. 문항은 정처기감자 두근두근 문제 복원에서 문항마다 공감이 가장 많은 복원을 골랐고, 문제 문장은 짧게 다시 썼다. 20문항 중 17문항은 사이트에서 '확정'으로 표시됐고 6·7·18번은 아직 편집 중이다. 공식 문제와 정답은 공개되지 않으므로 복원이 바뀌면 답도 바뀔 수 있다.
10-26에 복원 번호가 바뀌어 이 글도 새 번호로 고쳤다. C 함수 포인터 16→5번, C 이중 포인터 18→13번, 루트킷 5→16번, SBOM 19→18번, Python 상속 13→19번이다. 5·12·17·19번은 코드도 바뀌었지만 정답은 그대로다.
출제 분포(복원 기준)는 C 3 · Java 3 · Python 2 · SQL 3 · 이론 9문항이다. 아래 정답표로 가채점하고, 헷갈리는 문항은 문항별 해설에서 풀이 표와 자주 나오는 오답을 확인하면 된다.
한눈에
- 이론은 대부분 예전 기출이 그대로 다시 나왔다. 9문항 중 8문항이 이전 회차와 같은 문제나 같은 개념이다. 특히 2022년 3회에서만 세 문제(경계값 분석, FLSM 서브넷, UML 구성 요소)가 다시 나왔다. 처음 나온 용어는 18번 SBOM 하나다.
- 서브넷도 나왔다. “1·2회에 서브넷이 나왔으니 3회엔 안 나온다”는 말이 돌았지만 10번에 2022-3 문제가 그대로 나왔다.
- 코딩은 기본 개념의 동작 원리를 묻는 변형이 많았다. Python 가변 기본 인자(20번), C 구조체 배열 속 함수 포인터(5번)와 이중 포인터(13번), Java 2차원 배열의 행 공유(15번), 자식 필드와 super(14번), 오버로딩과 null(17번).
- SQL 3문항: LEFT JOIN + IS NULL(1번), ORDER BY 빈칸(3번, 2022-1과 같은 유형), 2단 서브쿼리 AVG + IN + MAX(12번).

가채점 정답표
| 번호 | 영역 | 정답 | 비고 |
|---|---|---|---|
| 1 | SQL | 2 | |
| 2 | DB 설계 | ① 물리적 ② 개념적 ③ 논리적 설계 | 2026-1·2023-2와 같은 개념 |
| 3 | SQL | ① ORDER ② score ③ DESC | 2022-1과 같은 유형 |
| 4 | 디자인 패턴 | Factory Method (보기 ㅅ) | 2021-3과 같은 개념 |
| 5 | C | 7 | 코드 순서 논쟁 있음 |
| 6 | 암호 | ① IDEA ② Skipjack | 2022-2와 같은 문제 |
| 7 | 테스트 | 경계값 분석 (보기 ㄹ) | 2022-3과 같은 유형 |
| 8 | 접근 통제 | RBAC | 2023-3·2025-3과 같은 개념 |
| 9 | C | 4 | |
| 10 | 서브넷 | 192.168.1.127 | 2022-3과 같은 문제 |
| 11 | UML | ① 관계 ② 클래스 ③ 인터페이스 | 2022-3과 같은 문제 |
| 12 | SQL | 130 | |
| 13 | C | 13 | |
| 14 | Java | 2 | |
| 15 | Java | 3 그리고 1 | |
| 16 | 보안 | 루트킷 (Rootkit, 보기 ㅅ) | 2024-1과 같은 개념 |
| 17 | Java | error | |
| 18 | 신기술 | SBOM | 처음 나온 용어 |
| 19 | Python | 48 | |
| 20 | Python | -2 |
보기 기호(ㅅ, ㄹ)는 복원자가 기억한 보기 순서를 따른 것이라 실제 시험지와 다를 수 있다.
문항별 정답과 해설
1. SQL — LEFT JOIN과 IS NULL (결과 값)
A
| id |
|---|
| 1 |
| 2 |
| 3 |
B
| id |
|---|
| 1 |
SELECT COUNT(*) AS result FROM A LEFT JOIN B ON A.id = B.id WHERE B.id IS NULL;
정답과 해설
정답: 2
LEFT JOIN은 '왼쪽(FROM에 먼저 쓴 A)의 행을 전부 남기고', 오른쪽에 짝이 없으면 오른쪽 칸을 NULL로 채운다.
| A.id | B.id |
|---|---|
| 1 | 1 |
| 2 | NULL |
| 3 | NULL |
WHERE B.id IS NULL → B에 짝이 없는 2, 3 → '2'. “한쪽에만 있는 행 찾기”는 OUTER JOIN + IS NULL이 공식이다. (Oracle 실행 확인)
- 오답 3: WHERE를 빼고 센 경우. 오답 1: INNER JOIN처럼 짝이 있는 행만 센 경우.
- 참고:
JOIN만 쓰면 INNER JOIN이고,LEFT JOIN은LEFT OUTER JOIN의 줄임이다.
2. DB — 설계 단계
다음 설명에 해당하는 데이터베이스 설계 단계를 쓰시오. (보기: 개념적 설계, 논리적 설계, 물리적 설계)
- ① 운영체제와 DBMS의 특성을 고려해 데이터베이스의 내부 저장 구조, 인덱스, 접근 방법 등을 설계하는 단계
- ② 요구사항 분석 결과를 바탕으로 특정 DBMS에 독립적인 데이터 모델을 설계하고 E-R 다이어그램으로 표현하는 단계
- ③ 관계형 데이터베이스의 테이블 구조와 스키마를 설계하는 단계
정답과 해설
정답: ① 물리적 설계 ② 개념적 설계 ③ 논리적 설계
전체 순서는 요구사항 분석 → '개념적' → '논리적' → '물리적' → 구현이다. 이번엔 순서를 섞어서 물었으니 단서 단어로 고른다.
| 단계 | 하는 일 | 단서 단어 |
|---|---|---|
| 개념적 설계 | 개체·관계 파악, E-R 다이어그램 | “E-R”, “특정 DBMS에 독립적” |
| 논리적 설계 | E-R을 관계 스키마(테이블)로 변환, 정규화 | “테이블 구조”, “스키마”, “관계형 데이터 모델” |
| 물리적 설계 | 저장 구조·인덱스·접근 경로 | “내부 저장 구조”, “인덱스”, “운영체제·DBMS 특성 고려” |
- 헷갈리는 지점: “DBMS”라는 말이 ①과 ②에 다 나온다. ②는 “DBMS에 '독립적'”이라 개념적 설계고, ①은 “DBMS 특성을 고려한 '저장 구조'”라 물리적 설계다. ③의 테이블 구조와 스키마는 저장 방식이 아니라 논리 구조라서 논리적 설계다.
3. SQL — ORDER BY 빈칸
STUDENT 테이블에서 학생의 이름과 점수를 점수가 높은 순서(내림차순)로 조회하도록 빈칸 ①~③을 채우시오.
STUDENT
| id | name | score |
|---|---|---|
| 1 | 김민수 | 80 |
| 2 | 이지은 | 95 |
| 3 | 박서준 | 70 |
| 4 | 최유진 | 85 |
SELECT name, score FROM STUDENT ( ① ) BY ( ② ) ( ③ );
정답과 해설
정답: ① ORDER ② score ③ DESC
ORDER BY 칼럼 DESC= 내림차순,ASC(또는 생략) = 오름차순.- 조회 결과는 이지은 95 → 최유진 85 → 김민수 80 → 박서준 70 순서다.
소문자로 order score desc라고 써도 되나?
된다.
- SQL 키워드(
ORDER BY,DESC)는 표준 SQL과 Oracle·MySQL·PostgreSQL 모두 대소문자를 구분하지 않는다.order by와ORDER BY는 같은 문장이다. - 따옴표로 감싸지 않은 칼럼 이름도 대소문자를 구분하지 않는다.
score와SCORE는 같은 칼럼이다. - 10-26 확정 복원은 ②를 테이블 그대로 소문자
score로 적었고, 대문자SCORE도 정답으로 인정될 것으로 추정했다. 공식 채점 기준은 공개되지 않는다. 감점 위험은 대소문자보다 철자(DESC→DECS)에 있다.
4. 디자인 패턴
다음 설명에 해당하는 GoF 디자인 패턴을 보기에서 고르시오.
생성 패턴 중 하나로, 객체를 생성하기 위한 인터페이스를 정의하고 '실제로 생성할 객체의 종류는 하위 클래스에서 결정'하도록 한다. 객체 생성 과정을 추상화해 구체적인 생성을 하위 클래스에 위임한다.
보기: ㄱ. Adapter ㄴ. Singleton ㄷ. Template Method ㄹ. Decorator ㅁ. Observer ㅂ. Strategy ㅅ. Factory Method ㅇ. Mediator
정답과 해설
정답: ㅅ. Factory Method (팩토리 메서드)
| 패턴 | 단서 |
|---|---|
| Factory Method | 생성할 객체를 하위 클래스가 결정 (생성 패턴) |
| Template Method | 알고리즘의 뼈대는 상위 클래스, 일부 단계만 하위 클래스가 구현 (행위 패턴) |
| Singleton | 객체를 하나만 생성 |
| Abstract Factory | 관련 객체들의 제품군을 한꺼번에 생성, 별칭 Kit (2026-2 2번) |
| Builder | 복잡한 객체를 단계별로 조립 |
| Prototype | 기존 객체를 복제해서 생성 |
- 함정: 보기에 Template Method가 있다. “하위 클래스”라는 말만 보면 둘 다 맞아 보이지만, “생성 패턴”과 “객체 생성”이 나오면 Factory Method다.
5. C — 구조체 배열 속 함수 포인터 (실행 결과)
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
typedef struct {
int value;
int (*fn)(int, int);
} Item;
int main(void) {
Item arr[2] = {{3, add}, {10, sub}};
Item *p = arr;
Item *ptr;
int a, b;
p++;
ptr = p;
a = p->value;
p--;
b = p->value;
printf("%d", ptr->fn(a, b));
return 0;
}
정답과 해설
정답: 7
읽는 법 세 줄:
Item *p = arr;→ p는 arr[0]을 가리킨다(배열 이름 = 첫 원소의 주소).p++→ '구조체 하나만큼' 이동해 arr[1]을 가리킨다(1바이트가 아니다).p->value는(*p).value— p가 가리키는 구조체의 멤버다.
| 줄 | p가 가리키는 것 | ptr이 가리키는 것 | 결과 |
|---|---|---|---|
Item *p = arr; | arr[0] = {3, add} | ||
p++ | arr[1] = {10, sub} | ||
ptr = p | arr[1] | arr[1] | p에 든 주소를 ptr에 복사 |
a = p->value | arr[1] | arr[1] | a = 10 |
p-- | arr[0] = {3, add} | arr[1] (그대로) | |
b = p->value | arr[0] | arr[1] | b = 3 |
ptr->fn(a, b) | arr[1]의 fn = sub | sub(10, 3) = 7 |
- 오답 13 (add(10, 3)): p–를 하면 ptr도 따라서 arr[0]으로 간다고 본 경우. ptr은 p와 '별개의 변수'다.
ptr = p순간 p에 들어 있던 주소(arr[1])를 복사해 둔 것뿐이라 p를 옮겨도 그대로다. 일반 변수 a가 p를 옮겨도 10으로 남는 것과 같다. - 오답 −7: a와 b를 바꿔 sub(3, 10)으로 계산한 경우.
“포인터로 받았으니 p를 따라가서 13 아닌가?”
코드의 순서에 달렸다. 네 가지 경우를 직접 실행한 결과:
| 코드 | 결과 |
|---|---|
구조체 포인터 ptr = p;를 p-- 전에 저장 (위 코드, 10-26 확정 복원) | 7 |
함수 포인터 ptr = p->fn;을 p-- 전에 저장 (10-25까지의 1위 복원) | 7 |
ptr = p;를 p-- 뒤에 저장 | 13 |
Item **ptr = &p; (p 자체를 가리키는 이중 포인터) | 13 |
포인터 변수도 대입하는 '순간의 주소를 복사'해 둘 뿐이라, 그 뒤에 p를 옮겨도 따라가지 않는다. 13이 되려면 대입이 p-- 뒤에 있었거나, p 자체의 주소(&p)를 담는 이중 포인터여야 한다. 10-26에 확정된 복원도 p-- 전에 저장하는 코드라 답은 7이다. 다만 이 복원에는 반대 표도 11개 붙어 있어 순서를 다르게 기억하는 응시자가 있다. 실제 시험지의 순서가 다르게 확인되면 답도 달라진다.
- 구조체 안의
int (*fn)(int, int)읽는 법: 괄호 안부터 — “fn은 '포인터', 가리키는 것은 int 두 개를 받아 int를 돌려주는 '함수'”. 구조체 포인터로 부를 때는ptr->fn(a, b).
6. 보안 — 대칭키 암호 알고리즘
괄호에 들어갈 대칭키 암호화 알고리즘을 쓰시오.
- ① Xuejia Lai와 James Massey가 제시한 암호화 알고리즘으로, PES와 IPES를 거쳐 ( ① )로 명명되었다. 128비트 키로 64비트 평문을 8라운드에 걸쳐 암호화한다.
- ② ( ② )는 미국 국가안보국(NSA)이 개발한 Clipper 칩에 내장된 블록 암호다. Fortezza 카드에 칩 형태로 구현되었고 주로 음성 암호화에 쓰인다. 64비트 입출력, 80비트 키, 32라운드.
정답과 해설
정답: ① IDEA ② Skipjack
- 2022년 2회와 같은 문제다.
- 단서만 기억하면 된다: “Lai·Massey, PES → IPES” → 'IDEA' / “NSA, Clipper 칩, Fortezza” → 'Skipjack'.
자주 나오는 대칭키 블록 암호 정리:
| 알고리즘 | 단서 |
|---|---|
| DES | 옛 미국 표준, 64비트 블록, 56비트 키 |
| AES | DES 대체, NIST 공모, 128비트 블록, 128/192/256비트 키 |
| SEED | 한국(KISA) 개발, 128비트 블록 |
| ARIA | 한국 국가 표준, 학·연·관 공동 개발 |
| IDEA | Lai·Massey, PES → IPES, 128비트 키, 8라운드 |
| Skipjack | NSA, Clipper 칩, 80비트 키, 32라운드 |
7. 테스트 — 블랙박스 기법
입력값의 범위에 따라 처리하는 프로그램의 테스트 데이터와 결과다. 적용된 블랙박스 테스트 기법을 보기에서 골라 기호를 쓰시오.
입력값은 0 이상 100 이하의 정수이고, 세 구간(0 이상 10 미만 / 10 이상 70 미만 / 70 이상 100 이하)으로 나누어 처리한다. 범위를 벗어난 입력값은 오류로 처리한다.
| 테스트 데이터 | -1 | 0 | 1 | 9 | 10 | 11 | 69 | 70 | 71 | 99 | 100 | 101 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 테스트 결과 | 오류 | 정상 | 정상 | 정상 | 정상 | 정상 | 정상 | 정상 | 정상 | 정상 | 정상 | 오류 |
보기: ㄱ. 동치 분할 테스트 ㄴ. 원인-결과 그래프 테스트 ㄷ. 결정 테이블 테스트 ㄹ. 경계값 분석 ㅁ. 오류 예측 테스트 ㅂ. 상태 전이 테스트 ㅅ. 유스케이스 테스트 ㅇ. 비교 테스트
정답과 해설
정답: ㄹ. 경계값 분석 (Boundary Value Analysis)
구간의 경계는 0, 10, 70, 100이다. 테스트 값이 이 '경계와 그 바로 앞뒤'(−1·0·1, 9·10·11, 69·70·71, 99·100·101)에 모여 있다. 경계 근처에서 오류가 많이 난다는 점을 노린 기법이다.
| 기법 | 테스트 값을 고르는 방식 | 이 문제였다면 |
|---|---|---|
| 경계값 분석 | 구간의 경계와 그 바로 옆 | −1, 0, 1, 9, 10, 11 … ← 이 문제 |
| 동등(동치) 분할 | 구간마다 대표값 하나 | 5, 40, 85 … 처럼 구간 안의 값 하나씩 (2026-2 5번) |
| 원인-결과 그래프 | 입력 조건(원인)과 출력(결과)의 논리 관계를 그래프로 그려 테스트 케이스 도출 | 표에 그래프·조건 조합이 나온다 |
| 결정 테이블 | 조건 조합 × 동작을 표로 정리 | 조건 Y/N 조합 표가 나온다 |
| 상태 전이 | 상태와 이벤트에 따른 전이를 따라감 | 상태 다이어그램이 나온다 |
| 오류 예측 | 경험·직관으로 오류가 날 만한 값 | |
| 비교 | 여러 버전에 같은 입력을 넣고 결과 비교 |
- 오답 원인-결과 그래프: “입력 → 결과(오류)” 표라서 원인·결과가 떠오르지만, 원인-결과 그래프는 조건들의 AND·OR 관계를 그래프로 그리는 기법이다. '값이 경계에 몰려 있으면 경계값 분석.'
8. 보안 — 접근 통제
다음 설명에 해당하는 접근 통제 방식을 보기에서 골라 쓰시오.
사용자에게 개별적으로 접근 권한을 부여하면 직책이 바뀔 때마다 권한이 누적되어 관리가 복잡해진다. 이를 해결하기 위해 '직책별로' 필요한 접근 권한을 정해 두고, 사용자는 자신에게 부여된 직책에 따라 자원에 접근하도록 한다.
보기(일부만 복원): MAC, DAC, RBAC, PBAC
정답과 해설
정답: RBAC (Role-Based Access Control, 역할 기반 접근 통제)
단서는 “직책별로 권한을 정해 두고 사람에게는 직책을 준다”다. 권한을 사람이 아니라 역할(Role)에 묶으니, 부서를 옮기면 역할만 바꾸면 되고 권한이 쌓이지 않는다.
| 방식 | 누가 권한을 정하나 | 단서 |
|---|---|---|
| DAC (임의적) | 자원의 소유자가 재량으로 | “소유자”, “신분(ID)에 따라” — 리눅스 chmod가 이 방식 |
| MAC (강제적) | 시스템이 보안 등급·레이블로 강제 | “보안 등급”, “비밀 취급 인가”, “소유자도 못 바꿈” |
| RBAC (역할 기반) | 역할에 권한을 묶고 사람에게 역할 부여 | “직무”, “직책”, “역할”, “부서 이동 시 역할만 변경” |
| PBAC (정책 기반) | 관리자가 정한 정책(규칙)으로 요청마다 판단 | “정책”, 시간·위치 같은 조건 |
- 복원자 메모: 실제 보기는 더 있었고, 기호로 답하는지 용어로 답하는지는 아직 확인 중이다.
9. C — 트리 순회와 카운터 (실행 결과)
#include <stdio.h>
typedef struct Node {
int value;
struct Node *left;
struct Node *right;
} Node;
int cnt = 0;
void pre(Node *n) {
if (!n) return;
if (++cnt == 3) {
printf("%d", n->value);
return;
}
pre(n->left);
pre(n->right);
}
int main(void) {
Node n4 = {4, NULL, NULL};
Node n5 = {5, NULL, NULL};
Node n2 = {2, &n4, &n5};
Node n3 = {3, NULL, NULL};
Node n1 = {1, &n2, &n3};
pre(&n1);
return 0;
}
정답과 해설
정답: 4
1단계 — 구조체를 트리로 옮겨 그린다. {값, 왼쪽, 오른쪽} 순서이므로 n2 = {2, &n4, &n5}는 “값 2, 왼쪽 n4, 오른쪽 n5″다.
n1(1)
/ \
n2(2) n3(3)
/ \
n4(4) n5(5)
2단계 — 함수가 언제 세는지 본다. ++cnt가 자식을 부르기 '전에' 있으므로 “자기 자신 → 왼쪽 → 오른쪽” 순서(전위 순회)로 센다. 함수 이름 pre도 전위 순회(preorder)라는 힌트다.
| 방문 | cnt | 출력 |
|---|---|---|
| n1 | 1 | |
| n2 | 2 | |
| n4 | 3 | 4 출력 후 return |
| n5 | 4 | |
| n3 | 5 |
if (!n) return;은 “'n이 NULL이면' 끝낸다”는 뜻이다.!n은 n == NULL일 때 참이다(n이 NULL이 아니면 무조건 return이라는 뜻이 아니다).- n4에서 return한 뒤에도 n2로 돌아가 n5를 방문한다. 하지만 cnt가 3이 아니라 아무것도 출력하지 않는다.
- 같은 트리라도
++cnt가 재귀 호출 '뒤'에 있으면(후위 순회, 2026-2 15번) n4 → n5 → n2 순이라 답은 2가 된다. 세는 줄의 위치가 답을 정한다.
10. 네트워크 — FLSM 서브네팅
192.168.1.0/24 네트워크를 FLSM 방식으로 3개 네트워크에 쓸 수 있도록 나누려 한다. IP Subnet-zero를 적용하며, 첫 번째 네트워크가 192.168.1.0/26일 때, 두 번째 네트워크의 브로드캐스트 주소를 10진수로 쓰시오.
정답과 해설
정답: 192.168.1.127
FLSM(모든 서브넷 크기가 같음) 계산 순서:
| 단계 | 할 일 | 이 문제 |
|---|---|---|
| ① 서브넷 비트 | 필요한 개수 이상이 되는 2의 거듭제곱 | 3개 → 2² = 4 → 2비트 (2¹ = 2는 부족) |
| ② 프리픽스 | 원래 + 서브넷 비트 | /24 + 2 = /26 (문제에도 주어짐) |
| ③ 블록 크기 | 2^(32 − 프리픽스) | 2⁶ = 64 |
| ④ 범위 | 0부터 블록 크기씩 | 1번 .0~.63 / 2번 .64~.127 / 3번 .128~.191 / 4번 .192~.255 |
| ⑤ 브로드캐스트 | 범위의 마지막 주소 | 192.168.1.127 |
- 네트워크 주소는 범위의 첫 주소(.64), 브로드캐스트는 마지막 주소(.127)다. 호스트에 줄 수 있는 건 그 사이(.65~.126)다.
- Subnet-zero 적용은 첫 서브넷(.0~.63)도 쓴다는 뜻이다. 문제에서 첫 네트워크를 192.168.1.0/26으로 준 것과 같은 말이다.
- 4개로 나뉘고 그중 3개를 쓴다. 정확히 3등분하는 것이 아니다.
- 2022년 3회와 같은 문제다.
11. UML — 구성 요소
UML 구성 요소에 대한 설명을 읽고 빈칸 ①~③에 알맞은 용어를 쓰시오.
UML의 기본 구성 요소는 사물(Things), ( ① ), 다이어그램(Diagrams)이다. 사물 중 정적 사물에는 클래스, 인터페이스, 통신, 유스케이스, 컴포넌트, 노드가 있다.
정적 사물 ( ② )는 동일한 속성·연산·관계·의미를 공유하는 객체들의 집합이다. (복원에는 Student 그림이 함께 있다: 속성 - name: String, 연산 + study(): void)
정적 사물 ( ③ )는 객체가 제공하는 서비스나 기능을 명세하는 연산들의 집합으로, 구현 내용은 포함하지 않는다. 표기할 때 스테레오타입 기호 << >>를 쓴다.
정답과 해설
정답: ① 관계 (Relationships) ② 클래스 (Class) ③ 인터페이스 (Interface)
- ① UML = 사물 + '관계' + 다이어그램.
- ② “같은 속성·연산을 공유하는 객체들의 '집합'” = 클래스(객체를 찍어 내는 틀). 그림의
-는 private,+는 public이다. - ③ “연산들의 집합, '구현은 포함하지 않음'” = 인터페이스. 다이어그램에서는 이름 위에
<<interface>>라고 적어 표시한다. 무엇을 할 수 있는지(메서드 이름·형태)만 정하고, 어떻게 하는지는 구현 클래스가 채운다. Java의interface가 바로 이것이다.
| 속성(필드) | 연산 이름 | 구현(몸통) | |
|---|---|---|---|
| 클래스 | 있음 | 있음 | 있음 |
| 인터페이스 | (상수만) | 있음 | 없음 |
- 2022년 3회와 같은 문제다.
12. SQL — 2단 서브쿼리 AVG·IN과 MAX (결과 값)
SALES
| YEAR | QUARTER | AMOUNT |
|---|---|---|
| 2023 | 1 | 100 |
| 2023 | 2 | 150 |
| 2023 | 3 | 130 |
| 2023 | 4 | 170 |
| 2024 | 1 | 120 |
| 2024 | 3 | 200 |
SELECT MAX(AMOUNT) AS RESULT
FROM SALES
WHERE YEAR = 2023
AND QUARTER IN (
SELECT QUARTER
FROM SALES
WHERE YEAR = 2023
AND AMOUNT < (
SELECT AVG(AMOUNT)
FROM SALES
WHERE YEAR = 2023
)
);
정답과 해설
정답: 130
가장 안쪽부터 바깥으로 푼다.
- 맨 안쪽: 2023년 AVG = (100 + 150 + 130 + 170) ÷ 4 = '137.5'
- 가운데: 2023년 중 AMOUNT < 137.5인 분기 → 1분기(100), 3분기(130) → QUARTER '(1, 3)'
- 바깥: YEAR = 2023 AND QUARTER IN (1, 3) → 100, 130
- 남은 행의 MAX → '130' (Oracle 실행 확인)
- 오답 200: 바깥의
YEAR = 2023을 놓친 경우. 분기 1·3에 2024년 행(120, 200)까지 들어가 MAX가 200이 된다(실행 확인). 2024년 행은 바깥 조건을 빠뜨리면 답이 바뀌게 만드는 함정 역할을 한다. - 오답 170:
<조건을 무시하고 2023년 전체 MAX를 쓴 경우. 서브쿼리 문제는 마지막에 '비교 기호의 방향'을 한 번 더 확인한다. - 복원자 메모: 2024년 두 행의 값과 가운데 서브쿼리의
YEAR = 2023은 원문 확인이 필요하다. 가운데 조건을 빼도, 평균을 6행 전체로 구해도 결과는 130으로 같다(실행 확인).
13. C — 이중 포인터와 함수 포인터 (실행 결과)
#include <stdio.h>
typedef struct Node {
int x;
int (*fn)(struct Node **);
} Node;
int incr(Node **pp) {
Node *n = *pp;
n->x = n->x + 1;
return n->x;
}
int fn_cnt(Node **pp) {
Node *p = *pp;
int a = p->fn(pp);
int b = p->fn(pp);
return b;
}
int main(void) {
Node a = {11, incr};
Node *p = &a;
Node **pp = &p;
printf("%d\n", fn_cnt(pp));
return 0;
}
정답과 해설
정답: 13
pp ──> p ──> a {x: 11, fn: incr}
*pp는 p,**pp는 구조체 a 자체다. incr 안의Node *n = *pp;는 n이 '원래 구조체 a를 가리키게' 할 뿐, 복사본을 만들지 않는다.p->fn(pp)는 a.fn, 즉 incr(pp)를 부른다.
| 호출 | a.x | 반환 |
|---|---|---|
| 처음 | 11 | |
첫 번째 p->fn(pp) | 12 | a = 12 |
두 번째 p->fn(pp) | 13 | b = 13 |
return b → '13'.
- 오답 12: 매번 11에서 시작한다고 본 경우. 포인터로 원본을 고치므로 값이 누적된다.
14. Java — 자식 필드와 super (실행 결과)
public class Main {
static class Parent {
int a = 1;
int meth() {
return a;
}
}
static class Child extends Parent {
int a = 1;
int meth() {
return a + super.a;
}
}
public static void main(String[] args) {
Parent obj = new Child();
System.out.println(obj.meth());
}
}
정답과 해설
정답: 2
- '메서드는 실제 객체 기준'(동적 바인딩)이다. 변수 타입은 Parent지만 실제 객체는 Child라서 Child.meth()가 불린다.
- Child.meth() 안의
a는 Child의 a(1),super.a는 Parent의 a(1) → 1 + 1 = '2'. - Child의 meth()에는
@Override가 없지만 이름과 매개변수가 부모와 같으니 오버라이딩이다.@Override는 “오버라이딩이 맞는지 컴파일러가 검사해 달라”는 표시일 뿐 동작을 바꾸지 않는다. - 오답 1: 변수 타입(Parent)을 보고 Parent.meth()가 불린다고 본 경우.
Java 바인딩 규칙 세 줄:
| 무엇 | 기준 | 정해지는 때 |
|---|---|---|
| 오버라이딩된 메서드 | 실제 객체 (@Override 표시가 없어도 같다) | 실행 시 |
필드 (obj.a) | 변수 타입 | 컴파일 시 |
| 오버로딩 중 어느 것 | 인자의 선언 타입 | 컴파일 시 |
그래서 obj.a를 직접 출력했다면 Parent의 a가 나왔을 것이다(여기선 값이 둘 다 1이라 같다).
15. Java — 2차원 배열의 행 공유 (실행 결과)
public class Main {
public static void main(String[] args) {
int[][] a = {{0}, {1}, {2}};
int[][] b = new int[3][];
for (int i = 0; i < a.length; i++) {
b[i] = a[i];
}
a[1] = new int[]{9};
b[2][0] = 3;
System.out.println(a[2][0] + " 그리고 " + b[1][0]);
}
}
정답과 해설
정답: 3 그리고 1
핵심: Java의 2차원 배열은 “행 배열들의 주소를 담은 배열”이다. a[0], a[1], a[2] 칸에는 값이 아니라 각 행(1차원 배열) 객체의 '주소'가 들어 있다.
① b[i] = a[i] — 주소를 복사한다
a ─┬─ [0] ──> 행X {0} <── [0] ─┬─ b
├─ [1] ──> 행Y {1} <── [1] ─┤
└─ [2] ──> 행Z {2} <── [2] ─┘
a와 b는 서로 다른 배열이지만 '같은 행 객체 세 개'를 가리킨다. “b[1]이 a[1]이라는 칸을 따라간다”가 아니라, 복사하는 '순간' a[1]에 들어 있던 주소(행Y)를 b[1]에 적어 둔 것이다.
② a[1] = new int[]{9} — a의 칸만 갈아끼운다
a[1] ──> 행W {9} (새로 만든 행)
b[1] ──> 행Y {1} (그대로)
a[1] 칸에 새 행의 주소를 넣었을 뿐이다. b[1] 칸에는 여전히 행Y의 주소가 들어 있다. 그래서 'b[1][0] = 1'.
③ b[2][0] = 3 — 공유 중인 행 안을 고친다
b[2]가 가리키는 행Z 안의 값을 바꿨다. a[2]도 같은 행Z를 가리키므로 'a[2][0] = 3'.
| 연산 | 종류 | 다른 쪽에 보이나 |
|---|---|---|
a[1] = 새 배열 | 칸에 든 주소를 갈아끼움 | 안 보인다 (b[1]은 옛 행) |
b[2][0] = 3 | 가리키는 행 안을 수정 | 보인다 (a[2]도 같은 행) |
- 오답
3 그리고 9: 갈아끼우기도 공유된다고 본 경우. 반대로 코드가a[1][0] = 9(행 안을 수정)였다면 b[1][0]도 9가 됐을 것이다. - 오답
2 그리고 1: 아무것도 공유하지 않는다고 본 경우(깊은 복사처럼). - Python의
b = a[:](얕은 복사)나[[0]*3]*3도 정확히 같은 원리다. 2026-1에 Python 얕은 복사가 처음 나왔는데, 이번엔 같은 원리를 Java로 물었다.
16. 보안 — 용어
다음 설명의 괄호 안에 공통으로 들어갈 보안 용어를 보기에서 고르시오.
공격자가 시스템에 침입한 뒤 '자신의 존재를 숨기면서' 시스템에 대한 접근 권한을 유지하기 위해 사용하는 도구 모음을 ( )이라고 한다. ( )은 백도어, 트로이 목마 등을 포함할 수 있으며, 원격 접근이나 사용 흔적 삭제 등 공격자가 지속적으로 시스템을 이용하는 데 필요한 기능을 제공한다. 또한 탐지하기 어려운 시스템의 깊은 레벨에서 작동하며, 정상적인 시스템 기능을 변조해 자신의 존재와 악성 프로그램의 실행을 숨긴다.
보기: ㄱ. Adware ㄴ. Virus ㄷ. Spyware ㄹ. Keylogger ㅁ. Ransomware ㅂ. Worm ㅅ. Rootkit ㅇ. Trace Route
정답과 해설
정답: ㅅ. Rootkit (루트킷)
- 단서: “관리자 권한(root) 유지” + “존재를 '숨김'” + “깊은 레벨에서 작동”.
- 백도어(몰래 다시 들어오는 뒷문)와 트로이 목마(정상 프로그램으로 위장)는 루트킷이 품을 수 있는 것들이다. 루트킷은 이것들을 숨겨 주는 도구다.
| 보기 | 하는 일 |
|---|---|
| Adware | 광고를 띄운다 |
| Virus | 다른 파일에 붙어 감염시킨다 (혼자 퍼지지 못함) |
| Spyware | 몰래 정보를 수집한다 |
| Keylogger | 키 입력을 기록한다 |
| Ransomware | 파일을 암호화하고 몸값을 요구한다 |
| Worm | 스스로 복제해 네트워크로 퍼진다 |
| Rootkit | 침입 흔적과 악성코드를 숨기고 권한을 유지한다 ← 정답 |
| Trace Route | 악성코드가 아니라 네트워크 경로를 추적하는 명령(tracert, traceroute) |
17. Java — 오버로딩과 NullPointerException (실행 결과)
public class Main {
static String convert(int value) {
Integer num = value;
return num.toString();
}
static String convert(double value) {
Double num = null;
if (value > 1) {
num = value * 2;
}
return num.toString();
}
public static void main(String[] args) {
try {
System.out.println(convert(1.0));
} catch (Exception e) {
System.out.println("error");
}
}
}
정답과 해설
정답: error (실행 확인)
| 단계 | 일어나는 일 |
|---|---|
convert(1.0) | 인자 1.0이 double이라 오버로딩 중 convert(double)이 골라진다 (컴파일 때 결정) |
Double num = null | 래퍼 클래스 Double 변수라 null을 담을 수 있다 |
value > 1 | double 1.0과 int 1을 비교 → 1.0 > 1은 거짓 (같을 뿐 크지 않다) |
num | 아무것도 대입되지 않아 null 그대로 |
num.toString() | null에 메서드를 부름 → NullPointerException |
catch (Exception e) | NullPointerException도 Exception의 하위 클래스라 잡힌다 → error 출력 |
“null”이 출력되는 것 아닌가?
아니다. 두 경우를 구분해야 한다.
| 코드 | 결과 |
|---|---|
System.out.println(num) (num이 null) | null 출력 — println이 null을 받으면 글자 “null”을 찍는다 |
String.valueOf(num) | "null" — null이면 글자 “null”을 돌려준다 |
num.toString() (이 문제) | 예외 발생 — null에는 부를 메서드가 없다. println까지 가지도 못한다 |
이 문제는 convert 안에서 num.toString()을 '직접' 부르므로, 값을 돌려주기 전에 예외가 난다.
1.0 > 1은 자료형이 달라서 에러 아닌가?
아니다. 숫자끼리 비교할 때 int 1이 double 1.0으로 자동 변환되어 정상 비교된다. 결과가 거짓일 뿐이다. 에러가 난 진짜 이유는 비교가 거짓이라 num이 null로 남았기 때문이다.
- 변형:
convert(2.0)이면 2.0 × 2 =4.0,convert(1)이면 정수라convert(int)가 골라져1이 출력된다(실행 확인). 어느 convert가 불릴지는 14번 표의 “오버로딩은 인자의 선언 타입, 컴파일 시” 규칙 그대로다.
18. 신기술 — 용어
다음 설명에 해당하는 용어를 보기에서 골라 쓰시오.
소프트웨어를 구성하는 오픈소스, 라이브러리 등의 구성 요소와 의존 관계를 기록한 메타데이터 목록이다. 소프트웨어의 구성 정보를 확인하고 취약점을 파악·관리하는 데 활용되며, 미국 연방정부에 소프트웨어를 공급할 때 제공을 요구하는 목록으로 언급된다.
보기: IAM, ZTA, SBOM, Privacy Sandbox, Cyber Resilience, RASP, Data Leakage Prevention, Cyber Kill Chain, Attack Surface
정답과 해설
정답: SBOM (Software Bill of Materials, 소프트웨어 자재 명세서)
- 제조업의 BOM(자재 명세서)을 소프트웨어에 적용한 것이다. “이 프로그램에 어떤 라이브러리 몇 버전이 들어 있나”의 목록이다.
- 2020년 SolarWinds 공급망 공격 뒤 2021년 5월 미국 행정명령(EO 14028)이 연방정부에 납품하는 소프트웨어에 SBOM 제공을 요구하기 시작했다. 같은 해 말 Log4j 취약점 사태 때 “우리 서비스에 Log4j가 들어 있나?”를 바로 답하지 못한 곳이 많아 필요성이 더 널리 알려졌다.
- 표준 형식으로 SPDX, CycloneDX가 있다. 실무로 치면
npm ls나pip freeze결과에 버전·라이선스를 붙여 정리한 것이다. - 이번 회차에서 유일하게 처음 나온 용어다. 단서는 “구성 요소와 의존 관계의 '목록'” + “'미국 연방정부' 공급 시 요구”.
나머지 보기도 신기술 단골이라 한 줄씩 정리한다.
| 보기 | 뜻 |
|---|---|
| IAM | 사용자 신원과 접근 권한 관리 |
| ZTA | 제로 트러스트 아키텍처 — “아무도 믿지 않고 매번 검증” |
| Privacy Sandbox | 서드파티 쿠키 없이 광고를 하려는 구글의 개인정보 보호 기술 묶음 |
| Cyber Resilience | 공격을 받아도 업무를 이어 가고 빨리 복구하는 능력 |
| RASP | 실행 중인 애플리케이션 안에서 공격을 탐지·차단하는 자가 보호 기술 |
| Data Leakage Prevention | 중요 데이터가 밖으로 새는 것을 탐지·차단 (DLP) |
| Cyber Kill Chain | 공격을 정찰부터 목표 달성까지 단계로 나눈 모델 |
| Attack Surface | 공격자가 노릴 수 있는 진입점 전체 (공격 표면) |
19. Python — 상속과 리스트 칸 수정 (실행 결과)
class NumberList:
def __init__(self):
self.data = [3, 5, 7, 9, 11]
def change(self, index, value):
if 0 <= index < len(self.data):
self.data[index] = value
class Calculator(NumberList):
def total(self):
total = sum(self.data)
return total
def increase(self):
for i in range(len(self.data)):
self.data[i] += 1
calculator = Calculator()
calculator.change(2, 15)
calculator.increase()
print(calculator.total(), end="")
정답과 해설
정답: 48
| 줄 | 일어나는 일 | calculator.data |
|---|---|---|
Calculator() | Calculator에 __init__이 없으므로 부모 NumberList의 __init__ 실행 | [3, 5, 7, 9, 11] |
change(2, 15) | 0 ≤ 2 < 5라 조건 통과 → 인덱스 2(세 번째 칸)를 15로 수정 | [3, 5, 15, 9, 11] |
increase() | 모든 칸에 1씩 더함 | [4, 6, 16, 10, 12] |
total() | 합계 | 4 + 6 + 16 + 10 + 12 = 48 |
- 오답 40: change를 놓친 경우 ([4, 6, 8, 10, 12]).
- 오답 43: increase를 놓친 경우 (3 + 5 + 15 + 9 + 11).
- 오답 50: 인덱스를 1부터 세어 두 번째 칸(5)을 15로 바꾼 경우 ([4, 16, 8, 10, 12]). 파이썬 인덱스는 0부터다.
end=""는 줄바꿈 없이 출력하라는 뜻이라 답에는 영향이 없다.- 부모에 정의된 change와 자식에 정의된 increase가 같은
self.data를 고친다. 메서드가 어느 클래스에 있든 self는 calculator 객체 하나다.
20. Python — 가변 기본 인자 (실행 결과)
def f(x=[1]):
x.append(x[-1] + 2)
return x[-1]
print(max(f(), f()) - min(f(), f()))
정답과 해설
정답: -2
핵심: 기본값 [1]은 함수를 정의하는 순간(def 줄이 실행될 때) 딱 한 번 만들어진다. 인자 없이 f()를 부를 때마다 새 [1]을 만드는 게 아니라, 그때 만든 '같은 리스트 하나'를 계속 쓴다. 그래서 append로 늘린 내용이 다음 호출에 남는다.
함수의 인자는 왼쪽부터 계산되므로 호출 순서는 이렇다.
| 호출 | x (호출 후) | x[-1] + 2 | 반환 |
|---|---|---|---|
max의 첫 f() | [1, 3] | 1 + 2 | 3 |
max의 둘째 f() | [1, 3, 5] | 3 + 2 | 5 |
min의 첫 f() | [1, 3, 5, 7] | 5 + 2 | 7 |
min의 둘째 f() | [1, 3, 5, 7, 9] | 7 + 2 | 9 |
max(3, 5) − min(7, 9) = 5 − 7 = '−2'.
- 오답 0: 부를 때마다
[1]에서 새로 시작한다고 본 경우 — 매번 3이 나와 3 − 3 = 0. - 직접 확인하는 법: 함수 정의 뒤에
print(f.__defaults__)를 찍어 보면([1],), f()를 두 번 부른 뒤 다시 찍으면([1, 3, 5],)로 기본값 자체가 늘어나 있다(실행 확인). - 실무에서는 이 동작이 버그의 원인이 되어서
def f(x=None):다음에if x is None: x = [1]처럼 쓴다. 시험은 이 함정을 그대로 낸 것이다. - 같은 원리: 기본값이 정수(
def f(n=0))처럼 바뀌지 않는 값이면n += 1은 새 값을 만들 뿐이라 누적되지 않는다. '리스트·딕셔너리처럼 안을 고칠 수 있는 기본값만' 누적된다.
마치며
복원은 아직 진행 중이고, 5번처럼 코드 순서를 두고 의견이 갈리는 문항도 있다. 복원이 바뀌면 이 글도 고친다. 정리하고 나서 가장 크게 남은 숫자는 이것이다. 이론 9문항 중 8문항이 예전 기출과 같은 문제나 개념이었다. 시험 전 이틀 동안 21회차 420문항을 분류하고 공부 순서를 다시 짠 과정은 정보처리기사 실기 2026년 3회 후기에 따로 적었다. 다음 시험을 준비한다면 이론 노트를 처음부터 읽는 대신 무엇부터 해야 할까?


