7. 구조체 심화

header


1. 구조체와 포인터

구조체 포인터는 구조체 변수의 주소를 저장하는 포인터입니다. 일반 포인터와 사용법이 유사하지만, 멤버 접근 방법이 다릅니다.

구조체 포인터 변수의 선언

일반 자료형 포인터와 동일한 방식으로 선언합니다.

// int형 포인터
int num;
int * ptr = #

// Person 구조체 포인터
typedef struct
{
    char name[30];
    int age;
} Person;

Person boy;
Person * ptr = &boy;

구조체 포인터를 통한 멤버 접근

구조체 포인터로 멤버에 접근하는 방법은 두 가지가 있습니다.

방법 1: * 연산자와 . 연산자 사용

(*ptr).age = 10;

방법 2: -> 연산자 사용 (화살표 연산자)

ptr->age = 10;
화살표 연산자 (->)
• 구조체 포인터 전용 연산자
(*ptr).멤버ptr->멤버는 동일
• 가독성이 좋아 많이 사용됨
• 직관적인 표현
#include <stdio.h>

typedef struct
{
    char name[30];
    int age;
} Person;

int main(void)
{
    Person boy = {"호날두", 35};
    Person * ptr = &boy;   // Person형 포인터 변수는 구조체 변수 boy를 참조

    // 다음 두 코드는 동일한 결과를 출력
    printf("%s (%d)\n", (*ptr).name, (*ptr).age);
    printf("%s (%d)\n", ptr->name, ptr->age);

    return 0;
}
실행 결과 보기
호날두 (35)
호날두 (35)

구조체의 멤버 변수로 선언된 포인터

포인터 변수도 구조체의 멤버로 선언될 수 있습니다.

#include <stdio.h>

typedef struct
{
    int x;
    int y;
} Point;

typedef struct
{
    Point * start;   // 구조체 포인터 변수 start
    Point * end;     // 구조체 포인터 변수 end
} Line;

int main(void)
{
    Point p1 = {10, 8};
    Point p2 = {20, 40};

    Line line = {&p1, &p2};

    // line의 멤버 변수 start와 end는 각각 포인터 변수
    printf("선의 시작점: [%d, %d]\n", line.start->x, line.start->y);
    printf("선의 끝점: [%d, %d]\n", line.end->x, line.end->y);

    return 0;
}
실행 결과 보기
선의 시작점: [10, 8]
선의 끝점: [20, 40]

메모리 구조:

Line 구조체 변수 line
┌────────────────────┐
│ start (Point *)    │──→ Point p1
│                    │    ┌────┬────┐
│                    │    │ x  │ y  │
│                    │    │ 10 │ 8  │
│                    │    └────┴────┘
│ end   (Point *)    │──→ Point p2
│                    │    ┌────┬────┐
└────────────────────┘    │ x  │ y  │
                          │ 20 │ 40 │
                          └────┴────┘
💡 구조체 포인터 멤버의 활용
• 다른 구조체를 간접적으로 참조
• 메모리 효율적인 데이터 구조 구현
• 연결 리스트, 트리 등의 자료구조에 활용
line.start->x: 구조체 → 포인터 → 멤버 순서로 접근

2. 구조체의 중첩

구조체의 멤버로 다른 구조체 변수를 선언할 수 있으며, 이를 구조체의 중첩이라고 합니다.

구조체 중첩의 기본

typedef struct
{
    char title[100];
    int published;
} Book;

typedef struct
{
    Book book;   // 멤버 변수로 구조체 변수 선언
} Bag;

중첩 구조체 초기화 및 접근

#include <stdio.h>

typedef struct
{
    char title[100];
    int published;
} Book;

typedef struct
{
    Book book;   // 멤버 변수로 구조체 변수 선언
} Bag;

int main(void)
{
    /*
     구조체 변수 선언과 동시에 초기화
     이때 멤버 변수 또한 선언과 동시에 초기화
    */
    Bag myBag = {
        {"지금 하지 않으면 언제 하겠는가", 2018}
    };

    // 멤버 변수로서의 구조체 변수에 접근할 때에도 사용 연산자는 동일
    printf("책 제목: %s\n출간년도: %d년\n",
           myBag.book.title, myBag.book.published);

    return 0;
}
실행 결과 보기
책 제목: 지금 하지 않으면 언제 하겠는가
출간년도: 2018년

접근 방식:

myBag.book.title

1. myBag 구조체 변수의 book 멤버에 접근
2. book 구조체 변수의 title 멤버에 접근

구조체 배열 멤버 변수

구조체 배열도 구조체의 멤버가 될 수 있습니다.

#include <stdio.h>

typedef struct
{
    char title[100];
    int published;
} Book;

typedef struct
{
    Book books[3];   // 멤버 길이 3인 구조체 배열 선언
} Bag;

int main(void)
{
    // 선언과 동시에 초기화
    Bag myBag = {
        {
            {"지금 하지 않으면 언제 하겠는가", 2018},
            {"타이탄의 도구들", 2017},
            {"12가지 인생의 법칙", 2018}
        }
    };

    int i;

    // 배열 요소에 대한 순차적 접근
    for(i = 0; i < 3; i++)
    {
        printf("책 제목: %s\n출간년도: %d년\n",
               myBag.books[i].title,
               myBag.books[i].published);
    }

    return 0;
}
실행 결과 보기
책 제목: 지금 하지 않으면 언제 하겠는가
출간년도: 2018년
책 제목: 타이탄의 도구들
출간년도: 2017년
책 제목: 12가지 인생의 법칙
출간년도: 2018년

메모리 구조:

Bag myBag
┌──────────────────────────────────────────┐
│ books[0]  │ books[1]  │ books[2]         │
├───────────┼───────────┼───────────       │
│ title     │ title     │ title            │
│ published │ published │ published        │
└──────────────────────────────────────────┘
⚠️ 구조체 중첩 주의사항
• 구조체 포인터 멤버 ≠ 구조체 멤버
• 포인터: 주소만 저장 (4 또는 8바이트)
• 직접 포함: 전체 구조체 크기만큼 메모리 할당
• 초기화 시 중괄호 중첩 필요

3. 구조체와 함수

구조체 변수를 함수의 인자로 전달하고 반환할 수 있습니다.

구조체를 인자로 전달 (Call-by-value)

#include <stdio.h>

typedef struct
{
    int s_id;
    int age;
} Student;

// 학번 및 나이를 인자로 받아 구조체 변수를 반환하는 함수
Student setStudent(int s_id, int age)
{
    Student s = {s_id, age};

    return s;
}

// 구조체를 전달받아 멤버 변수를 출력하는 함수
void printStudent(Student s)
{
    printf("학번: %d, 나이: %d\n", s.s_id, s.age);
}

int main(void)
{
    Student s = setStudent(20201212, 10);   // 반환된 구조체 변수를 대입
    printStudent(s);                        // 구조체 변수를 인자로 전달

    return 0;
}
실행 결과 보기
학번: 20201212, 나이: 10
Call-by-value 방식
• 구조체 변수 전체가 복사되어 전달
• 원본 데이터에 영향을 주지 않음
• 큰 구조체는 복사 비용이 클 수 있음
• 함수 내부에서 값을 변경해도 원본은 그대로 유지

구조체와 Call-by-reference

구조체 포인터를 사용하면 원본 구조체를 직접 수정할 수 있습니다.

#include <stdio.h>

typedef struct
{
    int xpos;
    int ypos;
} Point;

// 주소값을 참조하는 매개 변수
void setPosSameValue(Point * ptr)
{
    if(ptr->xpos > ptr->ypos)
        ptr->ypos = ptr->xpos;
    else
        ptr->xpos = ptr->ypos;
}

void printPoint(Point point)
{
    printf("x: %d, y: %d\n", point.xpos, point.ypos);
}

int main(void)
{
    Point position1 = {33, 66};
    Point position2 = {6, 3};

    // 주소값 전달
    setPosSameValue(&position1);
    setPosSameValue(&position2);

    // 구조체 출력
    printPoint(position1);
    printPoint(position2);

    return 0;
}
실행 결과 보기
x: 66, y: 66
x: 6, y: 6

동작 과정:

[position1 = {33, 66}일 때]

setPosSameValue(&position1) 호출
→ ptr->xpos (33) < ptr->ypos (66)
→ ptr->xpos = ptr->ypos
→ position1 = {66, 66} (원본 변경됨)

[position2 = {6, 3}일 때]

setPosSameValue(&position2) 호출
→ ptr->xpos (6) > ptr->ypos (3)
→ ptr->ypos = ptr->xpos
→ position2 = {6, 6} (원본 변경됨)
💡 Call-by-reference의 장점
• 원본 구조체를 직접 수정 가능
• 큰 구조체도 주소만 전달 (효율적)
• 메모리 복사 비용 절감
• 함수에서 여러 값을 반환하는 효과

사용 선택 기준
• 원본 수정 필요 → Call-by-reference
• 원본 보존 필요 → Call-by-value
• 큰 구조체 → Call-by-reference (효율성)

4. 종합 실습

문제 1 - 구조체 포인터 기본 (기초)

문제 1

다음 코드의 실행 결과는?

{% capture code_block1 %}

#include <stdio.h>

typedef struct {
    int x;
    int y;
} Point;

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

    ptr->x = 30;

    printf("%d", p.x);

    return 0;
}

{% endcapture %}

구조체 심화

문제 2 - 화살표 연산자 (기초)

문제 2

다음 중 올바른 구조체 포인터 접근 방법은?

A. ptr.member
B. ptr->member
C. (*ptr).member
D. B와 C 모두 올바르다.
구조체 심화

문제 3 - 구조체 중첩 (중급)

문제 3

다음 코드의 실행 결과는?

{% capture code_block3 %}

#include <stdio.h>

typedef struct {
    int score;
} Test;

typedef struct {
    Test math;
    Test english;
} Student;

int main(void) {
    Student s = {{90}, {85}};

    int total = s.math.score + s.english.score;

    printf("%d", total);

    return 0;
}

{% endcapture %}

구조체 심화

문제 4 - 구조체 배열 멤버 (중급)

문제 4

다음 코드의 실행 결과는?

{% capture code_block4 %}

#include <stdio.h>

typedef struct {
    int value;
} Data;

typedef struct {
    Data arr[3];
} Container;

int main(void) {
    Container c = {{{5}, {10}, {15}}};

    printf("%d", c.arr[1].value + c.arr[2].value);

    return 0;
}

{% endcapture %}

구조체 심화

문제 5 - Call-by-reference (중급)

문제 5

다음 코드의 실행 결과는?

{% capture code_block5 %}

#include <stdio.h>

typedef struct {
    int num;
} Data;

void modify(Data * ptr) {
    ptr->num *= 2;
}

int main(void) {
    Data d = {7};

    modify(&d);

    printf("%d", d.num);

    return 0;
}

{% endcapture %}

구조체 심화

문제 6 - 구조체 함수 종합 (고급)

문제 6

다음 코드의 실행 결과는?

{% capture code_block6 %}

#include <stdio.h>

typedef struct {
    int x;
    int y;
} Point;

Point createPoint(int x, int y) {
    Point p = {x, y};
    return p;
}

void doubleValues(Point * ptr) {
    ptr->x *= 2;
    ptr->y *= 2;
}

int main(void) {
    Point p = createPoint(3, 5);
    doubleValues(&p);

    printf("%d", p.x + p.y);

    return 0;
}

{% endcapture %}

{% capture hint6 %} createPoint(3, 5) → {3, 5} doubleValues → {6, 10} 6 + 10 = 16 {% endcapture %}

구조체 심화

핵심 요약

1. 구조체 포인터
• 선언: 구조체타입 * 포인터이름;
• 멤버 접근: 포인터->멤버 또는 (*포인터).멤버
• -> 연산자(화살표 연산자)가 가독성이 좋음
• 구조체 포인터 멤버 변수로 다른 구조체 참조 가능

2. 구조체 중첩
• 구조체의 멤버로 다른 구조체 선언 가능
• 접근: 외부구조체.내부구조체.멤버
• 초기화 시 중괄호 중첩 사용
• 구조체 배열도 멤버로 사용 가능
• 포인터 멤버와 직접 포함 멤버는 다름

3. 구조체와 함수
• Call-by-value: 구조체 전체 복사
• Call-by-reference: 구조체 주소 전달
• 함수 인자: void func(구조체타입 변수)
• 포인터 인자: void func(구조체타입 * 포인터)
• 구조체 반환: 구조체타입 func(...)

4. Call-by-value vs Call-by-reference
• Call-by-value: 원본 보존, 복사 비용 발생
• Call-by-reference: 원본 수정, 효율적
• 큰 구조체는 포인터 전달 권장
• 용도에 따라 적절히 선택

5. 실무 팁
• -> 연산자 사용으로 코드 간결성 향상
• 구조체 중첩으로 복잡한 데이터 구조 표현
• 함수에서 구조체 수정 필요 시 포인터 사용
• 연결 리스트, 트리 등에서 포인터 멤버 활용