본문 바로가기
Programming

C 구조체와 공용체

by 나무수피아는 지식의 가지를 뻗어가는 공간입니다. 2025. 8. 18.
반응형
구조체와 공용체 (Struct & Union)

구조체란?

구조체(struct)는 서로 다른 자료형의 변수들을 하나로 묶어서 사용할 수 있는 사용자 정의 자료형입니다. 즉, 관련된 여러 데이터를 하나의 이름으로 관리할 수 있도록 하는 매우 유용한 도구입니다. 예를 들어, 학생 정보를 관리할 때 이름, 나이, 성적과 같이 다양한 타입의 데이터를 하나의 단위로 묶어 효율적으로 다룰 수 있습니다. C언어에서 제공하는 기본 자료형(int, float, char 등)은 단일 데이터만 저장할 수 있지만, 구조체는 여러 필드를 포함해 현실 세계의 복합 데이터를 표현하기에 적합합니다.

구조체 정의 및 사용 예제

구조체는 struct 키워드를 사용해 정의하며, 각 멤버는 콤마가 아닌 세미콜론으로 구분합니다. 아래 예시는 Student라는 구조체를 정의하고, 이를 활용하는 간단한 코드입니다.

#include <stdio.h>

struct Student {
    char name[30];
    int age;
    float grade;
};

int main() {
    struct Student s1 = {"홍길동", 20, 4.2f};  // 구조체 변수 선언 및 초기화
    printf("이름: %s, 나이: %d, 성적: %.1f\n", s1.name, s1.age, s1.grade);
    return 0;
}
  

위 예제에서 Student 구조체는 이름(name), 나이(age), 성적(grade)을 멤버로 가지고 있습니다. s1 변수를 통해 한 학생의 정보를 저장 및 출력할 수 있습니다.

중첩 구조체 (Nested Struct)

구조체 내부에 또 다른 구조체를 멤버로 포함할 수 있는데, 이를 중첩 구조체라고 합니다. 복잡한 데이터 모델을 표현할 때 매우 유용합니다.

struct Date {
    int year;
    int month;
    int day;
};

struct Person {
    char name[20];
    struct Date birthday;  // Date 구조체를 멤버로 포함
};
  

예를 들어 Person 구조체가 Date 구조체를 포함하여 생일 정보를 함께 저장할 수 있습니다. 이렇게 하면 데이터의 논리적 구조를 명확히 하면서 재사용성도 높일 수 있습니다.

구조체와 포인터

구조체 변수의 메모리 주소를 저장하는 포인터를 선언해 사용할 수 있으며, 포인터를 통해 구조체 멤버에 접근할 때는 -> 연산자를 사용합니다. 이는 구조체가 메모리 상에 연속적으로 저장되기 때문입니다.

struct Student s2 = {"이몽룡", 22, 3.8f};
struct Student *ptr = &s2;
printf("이름: %s\n", ptr->name);  // ptr이 가리키는 구조체의 name 출력
  

위 코드에서 ptrs2의 주소를 저장하며, ptr->names2.name과 같은 결과를 출력합니다. 구조체 포인터를 사용하면 동적 메모리 할당 시에도 편리하게 멤버에 접근할 수 있습니다.

구조체 활용의 장점과 단점

  • 장점
    • 다양한 자료형을 묶어 복합 데이터를 표현 가능
    • 관련 데이터 그룹화를 통해 코드 가독성 향상
    • 재사용성이 높고 유지보수가 용이
  • 단점
    • 메모리 크기가 멤버들의 합과 같아 큰 구조체는 메모리 소모가 큼
    • 포인터 없이 값 복사가 이루어질 때 성능 저하 가능 (복사 비용 큼)
    • 내부 멤버 초기화 및 관리에 주의 필요

 

공용체 (Union)이란?

공용체(union)는 구조체와 비슷한 사용자 정의 자료형이지만, 모든 멤버가 동일한 메모리 공간을 공유한다는 점에서 큰 차이가 있습니다. 즉, 공용체의 메모리 크기는 가장 큰 멤버의 크기만큼 할당되며, 한 번에 하나의 멤버만 값을 가질 수 있습니다.

이러한 특징 덕분에 공용체는 메모리 절약이 매우 중요할 때 유용하게 사용됩니다. 다만, 동시에 여러 멤버를 저장할 수 없으므로 데이터 해석 시 주의가 필요합니다. 예를 들어, 어떤 멤버에 값을 저장한 뒤, 다른 멤버를 통해 접근하면 저장된 값이 손상되거나 의미 없는 값으로 해석될 수 있습니다.

공용체 선언과 사용 예시

#include <stdio.h>

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;
    data.i = 10;
    printf("정수: %d\n", data.i);

    data.f = 3.14f;  // i 값이 덮어써짐
    printf("실수: %.2f\n", data.f);

    // 문자열 저장 (가장 큰 멤버의 크기만큼 메모리 공유)
    snprintf(data.str, sizeof(data.str), "Hello, Union!");
    printf("문자열: %s\n", data.str);

    return 0;
}
  

위 예제에서 union Data는 세 가지 멤버를 가지고 있지만, 실제로는 하나의 메모리 공간을 공유합니다. data.i에 값을 할당한 뒤, data.f를 다시 할당하면 data.i 값은 덮었어집니다. 마지막으로 문자열을 저장하면 이전 값들은 모두 사라집니다.

공용체의 메모리 구조와 특징

  • 메모리 공유: 모든 멤버가 같은 시작 주소를 가지며, 크기는 가장 큰 멤버의 크기만큼 할당됨
  • 한 번에 하나의 데이터만 저장 가능: 여러 멤버를 동시에 저장할 수 없음
  • 메모리 절약 효과: 제한된 메모리 환경에서 유용
  • 데이터 해석 주의: 저장한 멤버와 다른 멤버를 통해 접근하면 값이 변형되어 보일 수 있음
  • 주로 하드웨어 레지스터, 프로토콜 패킷 처리 등에 사용

공용체와 구조체의 차이점 비교

구분 구조체 (struct) 공용체 (union)
메모리 할당 모든 멤버별로 각각 메모리 공간 할당 모든 멤버가 동일한 메모리 공간 공유
동시 저장 가능 데이터 개수 여러 멤버 모두 동시에 저장 가능 한 번에 한 멤버만 저장 가능
메모리 크기 멤버 크기의 합 가장 큰 멤버 크기와 동일
용도 복합 데이터 그룹화 메모리 절약, 하드웨어 제어, 타입 재해석
접근 방식 각 멤버 개별 접근 한 멤버만 의미 있음

공용체 활용 사례

공용체는 임베디드 시스템, 네트워크 프로토콜 처리, 시스템 레벨 프로그래밍 등 메모리 절약과 성능 최적화가 필요한 곳에서 널리 사용됩니다.

  • 하드웨어 레지스터 매핑: 여러 제어 비트가 동일한 메모리 공간에 겹쳐서 저장되는 경우
  • 네트워크 패킷 파싱: 프로토콜 헤더 필드가 여러 타입 중 하나일 때, 메모리 공간을 공유해 효율적으로 처리
  • 데이터 타입 변환: 같은 메모리를 정수, 실수, 문자 배열 등으로 다양하게 해석하는 경우

공용체 사용 시 주의 사항

공용체는 메모리 절약 측면에서는 장점이 많지만, 다음과 같은 점에 유의해야 합니다.

  • 한 멤버에 값을 저장한 뒤, 다른 멤버로 접근하면 값이 의미 없거나 손상됨
  • 공용체 내 데이터를 올바르게 해석하려면 현재 어떤 멤버가 유효한지 관리하는 로직 필요
  • 디버깅과 유지보수가 복잡해질 수 있음

 

구조체와 공용체 응용: 공통 사용 패턴

구조체와 공용체를 결합해 사용하는 경우도 많습니다. 특히, 공용체 멤버의 타입이 여러 가지일 때, 구조체 안에 공용체를 포함해 '태그' 변수로 어떤 타입이 저장됐는지 구분하는 태그드 유니언(tagged union) 패턴이 대표적입니다.

typedef enum {
    INT_TYPE,
    FLOAT_TYPE,
    STRING_TYPE
} DataType;

typedef struct {
    DataType type;
    union {
        int i;
        float f;
        char str[20];
    } data;
} Variant;

void printVariant(Variant *v) {
    switch(v->type) {
        case INT_TYPE: printf("정수: %d\n", v->data.i); break;
        case FLOAT_TYPE: printf("실수: %.2f\n", v->data.f); break;
        case STRING_TYPE: printf("문자열: %s\n", v->data.str); break;
        default: printf("알 수 없는 타입\n");
    }
}
  

위 예제는 Variant 구조체가 DataType이라는 태그로 현재 저장된 공용체 멤버 타입을 관리하여, 안전하게 데이터를 해석하고 출력하는 방법을 보여줍니다. 이러한 방식은 타입 안정성을 높이고 오류 가능성을 줄여 줍니다.

요약 및 마무리

구조체는 여러 데이터 타입을 하나의 그룹으로 묶어 복합 데이터 모델을 만들기에 적합하며, 각각의 멤버는 독립된 메모리 공간을 가집니다. 공용체는 메모리 공간을 공유하여 메모리 절약이 필요한 상황에 유리하지만, 한 번에 한 멤버만 의미 있는 데이터를 저장할 수 있다는 제약이 있습니다.

둘은 목적과 용도가 다르므로, 프로그램에서 요구하는 데이터 구조와 메모리 효율성에 따라 적절히 선택해 사용해야 합니다. 또한, 구조체와 공용체를 결합해 사용하는 태그도 유니언 같은 패턴을 활용하면 안전하고 확장성 있는 데이터 처리도 가능합니다.

C언어를 비롯한 많은 시스템 프로그래밍 언어에서 구조체와 공용체는 필수적인 자료형이며, 이들의 특성과 사용법을 명확히 이해하는 것은 안정적이고 효율적인 프로그램 개발의 기초가 됩니다.



 

반응형

'Programming' 카테고리의 다른 글

C 라이브러리와 헤더 파일  (58) 2025.08.20
C 전처리기  (47) 2025.08.19
C 파일 입출력  (53) 2025.08.17
C 포인터 기초  (63) 2025.08.16
C 배열과 문자열  (36) 2025.08.15