포인터쪽 증감,(주의,비트필드는 일단 패스)-공부 더 필요

류한선·2025년 6월 25일

실기연습-2

목록 보기
40/95

좋아요! 🎯
정보처리기사 실기에서 나오는 문제 중 간단하지만 함정이 있는 코드 문제를 연습용으로 준비해 볼게요.
이번 문제의 컨셉은 **"자료형, 연산자 우선순위, 증감 연산자 트릭"**을 섞어서 준비해봅니다.


🧠 문제

다음 Java 코드를 보고 프로그램의 출력값을 적으시오.
(포인트: 연산자 우선순위, 후치 증가, 전치 증가)

class Main {
    public static void main(String[] args) {
        int a = 10;
        int b = 5;
        int c = a++ + ++b * 2 - --a + b-- + a++ / b;
        System.out.println(c);
        System.out.println(a + " " + b);
    }
}

✅ 풀이 가이드

1️⃣ 코드를 한 줄씩 디버깅 해봅시다!

코드 상태:

int a = 10; // a=10
int b = 5;  // b=5
// 계산식: c = a++ + ++b * 2 - --a + b-- + a++ / b;

(1) a++

  • 후치 증가 연산자: 현재 a 값을 먼저 쓰고, 연산 끝나면 증가
  • 현재 값 10 반환 후, a → 11로 증가
  • 남은 상태: a=11, b=5

(2) ++b

  • 전치 증가 연산자: b를 먼저 1 증가시키고 값 반환
  • b → 6 반환 → ++b * 26 * 2 = 12
  • 남은 상태: a=11, b=6

(3) --a

  • 전치 감소 연산자: a를 먼저 1 감소시키고 값 반환
  • a → 10 반환 → - --a 부분 → -10
  • 남은 상태: a=10, b=6

(4) b--

  • 후치 감소 연산자: 현재 b 값 반환 후 b 감소
  • 현재 b=6 반환 후 b → 5
  • 남은 상태: a=10, b=5

(5) a++ / b

  • a++: 후치 증가 → 현재 a=10 반환, a → 11
  • b=5 (후치 감소 끝나고 남은 값)
  • 나눗셈 → 10 / 5 = 2
  • 남은 상태: a=11, b=5

2️⃣ 계산식 대입하기

이제 위 값들을 순서대로 대입합니다.

식:

c = (a++=10) + (++b * 2=12) - (--a=10) + (b--=6) + (a++/b=2)
c = 10 + 12 - 10 + 6 + 2
c = (10+12)=22; 22-10=12; 12+6=18; 18+2=20

3️⃣ 최종 상태 정리

  • a 최종값? 위 계산 중 a++가 두 번 있었죠.

    1. a++(맨 앞) → a=11
    2. --a → a=10
    3. a++/b → a++에서 a 반환 후 증가 → a=11
    • 최종 a=11
  • b 최종값? ++b에서 b=6, b--에서 b=5

    • 최종 b=5

🏁 정답 출력

c = 20
a b = 11 5

📝 정리

이 문제의 함정은 다음과 같아요:

✅ 후치 증가와 전치 증가의 동작 타이밍
✅ 연산자 우선순위 (++b * 2 먼저 계산됨)
✅ 같은 변수를 한 줄에서 여러 번 증감할 때의 상태 추적


좋아요! 이번에는 포인터와 메모리 참조에서 틀리기 딱 좋은 C언어 문제를 하나 준비해 드릴게요.
이런 문제는 주소 계산포인터 연산자, * 연산자 우선순위 때문에 많이 헷갈립니다.


🧠 문제 (C언어)

다음 코드를 보고 출력값을 예측해보세요.

#include <stdio.h>

int main() {
    int arr[3] = {10, 20, 30};
    int *p = arr;
    int **pp = &p;

    printf("%d\n", *p++);
    printf("%d\n", (*p)++);
    printf("%d\n", *++p);
    printf("%d\n", **pp);

    return 0;
}

🕵️ 트릭 포인트 미리 알리기

이 문제의 함정 포인트:

  1. *p++ vs (*p)++ → 연산자 우선순위
  2. ++pp++의 차이
  3. pp를 이용한 이중 포인터 참조

📝 정답 풀이 단계별 해설

🔵 초기 상태

int arr[3] = {10, 20, 30};
int *p = arr; // p → arr[0] (값 10)
int **pp = &p; // pp → p

🔵 printf("%d\n", *p++);

  • *p++*(p++)와 동일 (후치 증가 우선순위)
  • 현재 p가 arr[0]을 가리키므로 *p++*(p++) → 현재 *p 값(10)을 출력
  • 출력 후 p → arr[1] (p가 한 칸 증가됨)
  • 📤 출력값: 10

🔵 printf("%d\n", (*p)++);

  • 현재 p → arr[1] (값 20)
  • (*p)++ → 현재 p가 가리키는 값 20 반환 후, arr[1]의 값을 1 증가
  • arr[1] 값은 이제 21이 됨
  • 📤 출력값: 20

🔵 printf("%d\n", *++p);

  • *++p*(++p)와 동일
  • 현재 p → arr[1], ++p → arr[2]를 가리킴
  • *p → arr[2] → 값 30
  • 📤 출력값: 30

🔵 printf("%d\n", **pp);

  • pp → p → 현재 arr[2]를 가리킴
  • **pp → *p → arr[2] → 값 30
  • 📤 출력값: 30

🧮 최종 출력값 정리

10
20
30
30

좋아요! 🎯 이번에는 구조체 포인터 트릭, 함수 포인터 트릭, 문자(char) 포인터 트릭 세 가지를 준비해 드릴게요.
각 문제마다 코드와 함정 포인트, 그리고 해설까지 한 줄 한 줄 꼼꼼히 적어드릴게요.


🧠 1) 구조체 포인터 트릭 문제

🔍 문제

다음 C 코드를 보고 출력값을 예상해보세요.

#include <stdio.h>

struct Point {
    int x;
    int y;
};

int main() {
    struct Point p = {10, 20};
    struct Point *ptr = &p;

    printf("%d\n", ptr->x++);
    printf("%d\n", ++ptr->y);
    printf("%d\n", p.x + p.y);

    return 0;
}

💡 풀이

초기 상태:

  • p.x = 10, p.y = 20
  • ptr → \&p

한 줄씩 디버깅

  1. printf("%d\n", ptr->x++);

    • 후치 증가 연산자 ptr->x++ → 현재 x(10) 출력 후 p.x를 11로 증가
    • 출력값: 10, p.x = 11
  2. printf("%d\n", ++ptr->y);

    • 전치 증가 연산자 → p.y를 1 증가(20→21) 후 출력
    • 출력값: 21, p.y = 21
  3. printf("%d\n", p.x + p.y);

    • 현재 p.x=11, p.y=21
    • 출력값: 32

✅ 정답:

10
21
32

🧠 2) 함수 포인터 트릭 문제

🔍 문제

다음 코드를 보고 출력값을 예상해보세요.

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}
int mul(int a, int b) {
    return a * b;
}
int main() {
    int (*func)(int, int);  // 함수 포인터 선언
    func = add;
    printf("%d\n", func(2, 3));
    func = mul;
    printf("%d\n", (*func)(2, 3));
    return 0;
}

💡 풀이

  1. func = add; → func가 add를 가리킴
  2. func(2,3) 호출 → add(2,3) → 2+3=5 출력
  3. func = mul; → func가 mul을 가리킴
  4. (*func)(2,3) 호출 → mul(2,3) → 2*3=6 출력

✅ 정답:

5
6

🧠 3) 문자(char) 포인터 트릭 문제

🔍 문제

다음 코드를 보고 출력값을 예상해보세요.

#include <stdio.h>

int main() {
    char str[] = "HELLO";
    char *p = str;

    printf("%c\n", *p++);
    printf("%c\n", *++p);
    printf("%c\n", *(p+1));
    printf("%c\n", p[-1]);

    return 0;
}

💡 풀이

초기 상태:

  • str = {'H','E','L','L','O','\0'}
  • p → str0

한 줄씩 디버깅

  1. printf("%c\n", *p++);

    • *(p++) → 현재 *p='H' 출력 후 p→str[1]
    • 출력값: H
    • 상태: p→str1
  2. printf("%c\n", *++p);

    • *(++p) → p→str[2], *p='L' 출력
    • 출력값: L
    • 상태: p→str2
  3. printf("%c\n", *(p+1));

    • 현재 p→str2
    • p+1→str3
    • 출력값: L
  4. printf("%c\n", p[-1]);

    • p[-1]→*(p-1)→str1
    • 출력값: E

✅ 정답:

H
L
L
E

좋아요! 🎯 이번에는 더 어려운 트릭 문제들을 준비해 드릴게요.
이번엔 비트 연산 트릭동적 메모리 트릭 문제를 각 1개씩 준비합니다.
이번에도 문제 코드 + 함정 포인트 + 자세한 풀이까지 한 줄 한 줄 설명해 드릴 거예요.
실전에선 이런 문제에서 오답 나기 쉽습니다. 꼭 따라가며 연습해 보세요!


🧠 1) 비트 연산 트릭 문제

🔍 문제

다음 C 코드를 보고 출력값을 예상해보세요.

#include <stdio.h>

int main() {
    unsigned char a = 0xF0;  // 11110000
    unsigned char b = 0x0F;  // 00001111
    unsigned char c = a & b;
    unsigned char d = a | b;
    unsigned char e = a ^ b;
    unsigned char f = ~a;

    printf("%02X\n", c);
    printf("%02X\n", d);
    printf("%02X\n", e);
    printf("%02X\n", f);

    return 0;
}

💡 풀이 단계

초기값

  • a = 0xF0 = 11110000(2)
  • b = 0x0F = 00001111(2)

계산

  1. c = a & b: 비트 AND

    • 11110000 & 00001111 = 00000000(2) = 0x00
  2. d = a | b: 비트 OR

    • 11110000 | 00001111 = 11111111(2) = 0xFF
  3. e = a ^ b: 비트 XOR

    • 11110000 ^ 00001111 = 11111111(2) = 0xFF
  4. f = ~a: 비트 NOT

    • ~11110000(2) = 00001111(2) = 0x0F

✅ 정답:

00
FF
FF
0F

🧠 2) 동적 메모리 트릭 문제

🔍 문제

다음 C 코드를 보고 출력값을 예상해보세요.

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = (int*)malloc(3 * sizeof(int));
    p[0] = 5;
    p[1] = 10;
    p[2] = 15;

    int *q = p;
    free(p);

    printf("%d\n", q[1]);
    q[1] = 20;
    printf("%d\n", q[1]);

    return 0;
}

💡 풀이 단계

초기 상태:

  • p에 정수 3개 동적 메모리 할당 → [5, 10, 15]
  • q = p; → q와 p가 같은 메모리 참조
  • free(p); 호출 → p의 메모리는 반환되었지만 q도 같은 주소를 보고 있음.
    ⚠️ 이제 q는 유효하지 않은 메모리를 참조하는 댕글링 포인터(Dangling pointer) 상태!

트릭:

  • q[1]을 읽거나 쓰는 것은 **정의되지 않은 동작(UB)**이므로 무슨 값이 출력될지 알 수 없음.
  • 프로그램마다 다릅니다.
  • 하지만 많은 환경에서 free 이후 메모리 내용은 우연히 남아 있기도 하므로, 대개 q[1] 출력 시 10 또는 비슷한 값이 나올 수 있지만 보장되지 않아요.

중요한 포인트:

  • 이미 free(p); 했으므로 q를 쓰면 안 됩니다. 반드시 q=NULL; 처리를 하거나 새 메모리를 할당해야 함.

✅ 정답:

이 문제의 정답은 “정의되지 않은 동작(Undefined Behavior)”입니다.
단순히 10 또는 20이 나올 수 있지만, 믿어선 안 되고 프로그램마다 다릅니다.
이런 문제에서 중요한 건 포인터가 해제된 후 사용되는 경우 위험함을 인지하는 것입니다.


좋아요! 🎯 이번에는 더 고급 트릭 문제들 준비해 드릴게요.
이번엔 구조체 안에 포인터 트릭, 비트필드 트릭, 함수 포인터 배열 트릭 3가지를 연습해봅시다.


🧠 1) 구조체 안에 포인터 트릭 문제

🔍 문제

다음 C 코드를 보고 출력값을 예상해 보세요.

#include <stdio.h>

struct Data {
    char *msg;
};

int main() {
    char str1[] = "Hello";
    char str2[] = "World";
    struct Data d1 = {str1};
    struct Data d2 = {str2};

    d2 = d1;           // 구조체 대입
    d1.msg[0] = 'J';   // str1[0] = 'J'

    printf("%s\n", d2.msg);
    return 0;
}

💡 풀이 단계

  1. struct Data d1 = {str1}; → d1.msg → "Hello"
  2. struct Data d2 = {str2}; → d2.msg → "World"
  3. d2 = d1; → 구조체 대입 → d2.msg = d1.msg → 이제 d2.msg가 str1을 참조
  4. d1.msg[0] = 'J'; → str1[0] = 'J' → str1 = "Jello"
  5. d2.msg도 str1을 참조 중이므로 "Jello" 출력됨.

✅ 정답:

Jello


🧠 2) 비트필드 트릭 문제

🔍 문제

다음 C 코드를 보고 출력값을 예상해 보세요.

#include <stdio.h>

struct BitData {
    unsigned int a : 3;
    unsigned int b : 5;
};

int main() {
    struct BitData bd;
    bd.a = 7;   // 3비트, 최대값 7(111b)
    bd.b = 32;  // 5비트, 32(100000b)지만 5비트니 모드 연산됨

    printf("%u %u\n", bd.a, bd.b);
    return 0;
}

💡 풀이 단계

  1. bd.a = 7; → 3비트 필드 최대값은 7 → a=7

  2. bd.b = 32; → 5비트 필드(0~31까지 표현 가능)

    • 32 % 2^5 = 32 % 32 = 0 → b=0
  3. 출력값: 7 0


✅ 정답:

7 0


🧠 3) 함수 포인터 배열 트릭 문제

🔍 문제

다음 C 코드를 보고 출력값을 예상해 보세요.

#include <stdio.h>

int add(int x, int y) { return x + y; }
int sub(int x, int y) { return x - y; }
int mul(int x, int y) { return x * y; }

int main() {
    int (*ops[3])(int, int) = { add, sub, mul };
    int a = 10, b = 3;

    printf("%d\n", ops[2](a, b));
    printf("%d\n", (*ops[1])(a, b));
    printf("%d\n", (**ops)(a, b));
    return 0;
}

💡 풀이 단계

  • ops[0] = add, ops[1] = sub, ops[2] = mul
  • ops[2](a,b) = mul(10,3) = 30
  • (*ops[1])(a,b) = sub(10,3) = 7
  • (**ops)(a,b) = add(10,3) = 13
    (ops 자체가 배열의 시작주소이므로 *ops == ops[0] == add**ops == add)

✅ 정답:

30
7
13

좋아요! 🔍 비트필드 트릭 문제를 아주 자세히 디버깅해 드릴게요.
이 부분에서 헷갈리신 거라면, 비트필드의 크기와 값 저장 방식을 한 줄 한 줄 디버깅하는 방식으로 확실히 정리해봅시다.


🔍 비트필드 트릭 문제 (원본)

#include <stdio.h>

struct BitData {
    unsigned int a : 3;  // a를 3비트로 저장
    unsigned int b : 5;  // b를 5비트로 저장
};

int main() {
    struct BitData bd;
    bd.a = 7;   // 3비트 필드
    bd.b = 32;  // 5비트 필드

    printf("%u %u\n", bd.a, bd.b);
    return 0;
}

📚 비트필드가 뭐지?

unsigned int a : 3;
a3비트 크기로 제한된 필드

unsigned int b : 5;
b5비트 크기로 제한된 필드

여기서 중요한 점:
✅ 비트필드에 값을 대입하면 지정된 비트 수만 남아요.
✅ 값이 비트를 초과하면 나머지는 버려지고, 2^n으로 나눈 나머지가 남아요.


🧮 계산 과정 자세히 디버깅하기

1️⃣ bd.a = 7;

  • a의 비트 수: 3비트
  • 3비트가 표현할 수 있는 수의 범위: 0~7 (2^3 - 1)
  • 대입할 값: 7 → 이진수 111 (3비트)
  • 7은 3비트로 딱 맞아요. → a = 7 저장

2️⃣ bd.b = 32;

  • b의 비트 수: 5비트
  • 5비트가 표현할 수 있는 수의 범위: 0~31 (2^5 - 1)
  • 대입할 값: 32 → 이진수로 100000 (6비트 짜리 수)
  • b 필드의 크기(5비트)에 담을 수 있는 최대값: 31 (11111)
  • 그런데 32를 5비트로 담으려면 어떻게 해야 하나?

🤔 잘리는 과정

  • 32를 5비트로 잘라보자:
  • 32 % 2^5 = 32 % 32 = 0

✅ 비트필드 내부 동작:
필드에 값이 대입될 때 비트 크기만큼 잘리므로
32 (100000₂)에서 상위 비트를 버리고 00000₂(0)만 남아요.


🎯 결론

  • bd.a = 7
  • bd.b = 0

→ 출력:

7 0

🎮 추가 팁

비트필드에 값을 대입할 때 이렇게 잘리는 이유는 C 언어 표준에서 비트필드 대입 시 지정된 비트 수만큼만 메모리에 남긴다고 규정하기 때문이에요.
이건 하드웨어 레지스터 같은 곳에 한정된 비트를 쓰도록 설계할 때 유용하지만,
일반 정수 대입할 때는 오버플로우(잘림)가 발생할 수 있다는 점을 꼭 기억하세요.


0개의 댓글