구조체 연습 3

류한선·2025년 7월 4일

실기연습-2

목록 보기
85/95

좋아, 그러면 아까 문제들 중 하나를 선택해서,
내가 네가 요청한 기준대로 한 줄씩 해부해줄게:

  • 그 줄이 무슨 역할인지
  • 왜 그 위치에 있어야 하는지
  • 실행 순서에서 무슨 일이 벌어지는지
  • 예제를 넣어서 어떤 값이 처리되는지
  • 값이 시각적으로 어떻게 변하는지
  • 디버깅 흐름처럼 천천히

🔧 우선 이 문제부터 완전 해부할게:

✅ 주제: 문자열 복사 없이 shallow copy 후 오류 유도

문제 3: 문자열 복사/포인터 공유 트랩

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

typedef struct {
    char* name;
} User;

int main() {
    User u1;
    u1.name = malloc(10);
    strcpy(u1.name, "Jack");

    User u2 = u1;  // shallow copy
    free(u1.name);

    printf("%s\n", u2.name);  // ?
    return 0;
}

✅ 한 줄씩 해설 시작


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
  • 🔧 stdio.h: printf 사용 위해 필요
  • 🔧 stdlib.h: malloc, free 사용 위해 필요
  • 🔧 string.h: strcpy 사용 위해 필요
  • 📌 이 줄들은 반드시 가장 위에 있어야 컴파일 에러가 안 남

typedef struct {
    char* name;
} User;
  • 🧱 User라는 구조체 정의
  • ✨ 이 구조체는 name이라는 문자열 포인터 한 개만 가짐
  • 📌 char* name은 단순한 문자열이 아니라 외부 동적 메모리를 가리키는 포인터
    → 즉, 실제 문자열은 다른 곳에 있고, name은 그 주소만 가지고 있음

int main() {
  • 프로그램의 진입점
  • 여기부터 실행 시작

    User u1;
  • 🧍 구조체 변수 u1 선언
  • 이 시점엔 u1.name은 초기화 안 됨 → 쓰레기 값
u1 = { name: ??? }

    u1.name = malloc(10);
  • 💡 name이 가리킬 메모리 공간을 힙(heap)에 10바이트만큼 할당

  • 예: 주소 0x1000이라고 가정하면

    u1.name → 0x1000
    0x1000 공간은 비어 있음

    strcpy(u1.name, "Jack");
  • 🔁 문자열 "Jack"을 u1.name이 가리키는 공간에 복사

  • 복사 결과:

    0x1000: 'J' 'a' 'c' 'k' '\0'
    u1.name → 0x1000

    User u2 = u1;  // shallow copy
  • ✨ 구조체 전체를 값 복사
  • u2.name = u1.name;이 됨
    → 즉, u1.name, u2.name 모두 같은 주소 0x1000을 가리킴
u1.name ─┐
         ▼
       "Jack"
         ▲
u2.name ─┘
  • 💣 여기서 실수: name이 포인터인데, 복사하면서도 같은 주소 공유함 (얕은 복사)

    free(u1.name);
  • 🔥 0x1000 주소의 메모리 해제
  • 하지만 u2.name은 여전히 0x1000을 가리키고 있음 → dangling pointer
u1.name ─┐
         ▼
       [FREE]
         ▲
u2.name ─┘  ← 여전히 같은 주소

    printf("%s\n", u2.name);  // ?
  • 📢 u2.name을 출력하려 함

  • 하지만 u2.name이 가리키는 주소는 이미 해제됨

  • 💥 결과: undefined behavior

    • 세그폴트 날 수도 있고
    • 쓰레기 문자열이 출력될 수도 있음
    • 어떤 시스템에서는 "Jack"이 정상 출력될 수도 있음 (하지만 그건 "운")

    return 0;
}
  • 함수 정상 종료

🧠 시각화 요약

u1.name = malloc(10);    // ─┐
strcpy(u1.name, "Jack"); //   ├→ "Jack" 저장
User u2 = u1;            // ─┘ u2.name도 같은 주소
free(u1.name);           // 메모리 해제됨
printf("%s\n", u2.name); // 이미 해제된 곳을 출력 → 🚫 위험!

❌ 왜 이게 위험한가?

  • User u2 = u1; 시 포인터도 같이 복사되었는데
  • free(u1.name);을 먼저 해버려서, u2.name이 dangling pointer가 됨
  • 이후에 그 주소를 사용하면 정확한 결과를 보장할 수 없음

✅ 안전하게 고치려면?

User u2;
u2.name = malloc(strlen(u1.name) + 1); // 별도 공간 할당
strcpy(u2.name, u1.name);              // 실제 내용 복사

→ 이렇게 하면 u2는 별도의 메모리를 가지므로, u1과 메모리 공유 문제 없음


좋아! 아까 네가 요청한 나머지 문제들에 대해
앞선 스타일 그대로, 한 줄씩 디버깅 하듯 완전 해부해서 설명해줄게.


✅ 문제 1: Node** 이중 포인터로 참조 바꾸기

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

typedef struct Node {
    int val;
    struct Node* next;
} Node;

void changeHead(Node** head) {
    Node* newNode = malloc(sizeof(Node));
    newNode->val = 99;
    newNode->next = *head;
    *head = newNode;
}

int main() {
    Node* head = malloc(sizeof(Node));
    head->val = 1;
    head->next = NULL;

    changeHead(&head);

    printf("%d -> %d\n", head->val, head->next->val);
    return 0;
}

✅ 1. 코드 전체 흐름 미리 요약

- main()에서 head 생성 (val = 1)
- changeHead()로 주소를 넘겨서
  → 새로운 노드 99가 맨 앞에 붙음
- printf()는 99 → 1을 출력하게 됨

✅ 한 줄씩 해부

typedef struct Node {
    int val;
    struct Node* next;
} Node;
  • 구조체 Node 정의
  • 멤버는 정수 val과 다음 노드를 가리키는 포인터 next
  • 자기 자신을 가리키는 구조 → 연결 리스트 노드

void changeHead(Node** head) {
  • Node** → 포인터의 주소를 받음
    예) Node* head = ...;
    &headNode**
  • 이걸 통해 함수 안에서 head 자체를 바꿀 수 있음

    Node* newNode = malloc(sizeof(Node));
  • 새로운 노드를 생성
  • 예: newNode가 0x5000에 있다고 하자

    newNode->val = 99;
  • 생성한 노드에 값 99 저장
0x5000 (newNode):
    val = 99

    newNode->next = *head;
  • *head는 원래의 head를 가리킴 → 즉, main의 head
  • newNode의 next가 원래 노드를 가리키도록 함
newNode(99) → head(1)

    *head = newNode;
  • 이제 main()의 head가 newNode를 가리키도록 바뀜
  • 이로써 실제 head가 새 노드로 대체됨

Node* head = malloc(sizeof(Node));
head->val = 1;
head->next = NULL;
  • 원래 head는 val=1, next=NULL인 노드
  • 주소: 예) 0x4000
head (main) → 0x4000:
    val = 1
    next = NULL

changeHead(&head);
  • &head를 넘겼으므로 head 자체가 바뀜
  • now: head → newNode(99) → oldHead(1)

printf("%d -> %d\n", head->val, head->next->val);
  • head->val = 99
  • head->next->val = 1

✅ 출력: 99 -> 1


✅ 시각화 요약

Before:
head → [val=1] → NULL

After changeHead:
head → [val=99] → [val=1] → NULL

✅ 문제 2: 포인터 배열(dict) key/value

#include <stdio.h>

int main() {
    char* keys[] = {"apple", "banana", "cherry"};
    char* values[] = {"red", "yellow", "dark red"};

    char** dict[3];
    for (int i = 0; i < 3; i++) {
        dict[i] = malloc(2 * sizeof(char*));
        dict[i][0] = keys[i];
        dict[i][1] = values[i];
    }

    printf("%s -> %s\n", dict[1][0], dict[1][1]);
    return 0;
}

✅ 해부: 구조

dict[1][0] = "banana"   // key
dict[1][1] = "yellow"   // value

한 줄씩 설명

char* keys[] = {"apple", "banana", "cherry"};
  • 문자열 포인터 배열
  • 각 요소가 문자열을 가리킴
keys[0]"apple"
keys[1]"banana"
keys[2]"cherry"

char* values[] = {"red", "yellow", "dark red"};
  • 위와 동일 방식
values[0]"red"
values[1]"yellow"
values[2]"dark red"

char** dict[3];
  • dict는 포인터 배열 (3칸)
  • 각 칸에 "key", "value"를 묶는 포인터 배열 넣음
    즉, dict[0]char*[2] (key, value)

for (int i = 0; i < 3; i++) {
    dict[i] = malloc(2 * sizeof(char*));
    dict[i][0] = keys[i];
    dict[i][1] = values[i];
}
  • 반복문으로 각 dict[i]에 메모리 할당
  • 각각 "key", "value"를 넣음

예: i = 1일 때

dict[1][0] = keys[1] = "banana"
dict[1][1] = values[1] = "yellow"

printf("%s -> %s\n", dict[1][0], dict[1][1]);
  • 출력: "banana -> yellow"

✅ 정답: banana -> yellow


✅ 문제 3: 문자열 복사 없이 shallow copy 후 해제

(이건 앞에서 이미 해부했음)

📌 핵심 요약:

User u2 = u1;  // u2.name도 같은 포인터 가짐
free(u1.name); // u2.name도 해제된 주소 가리킴
printf("%s", u2.name); // → 쓰레기 값, 세그폴트 가능

🧠 전체 정리

문제핵심 포인트트랩 요약정답 결과
Node** 함수 인자이중 포인터로 head 변경원본 주소까지 바뀜99 -> 1
포인터 배열 dict2중 포인터 배열 구조key/value 구성 복잡banana -> yellow
shallow copy + free같은 주소 공유 후 freedangling pointer런타임 에러 가능

0개의 댓글