Language/C

[C] C언어 구조체 - 개념, 포인터, 활용, 공용체와 열거체

재은초 2026. 6. 8. 12:48
728x90
반응형

구조체란?

  • 구조체(structure type)란 사용자가 C언어의 기본 타입을 가지고 새롭게 정의할 수 있는 사용자 정의 타입이다.
  • 구조체는 기본 타입만으로는 나타낼 수 없는 복잡한 데이터를 표현할 수 있다.
  • 배열이 같은 타입의 변수 집합이라고 한다면, 구조체는 다양한 타입의 변수 집합을 하나의 타입으로 나타낸 것이다. 이때 구조체를 구성하는 변수를 구조체의 멤버(member) 또는 멤버 변수(member variable)라고 한다.

 

구조체의 정의와 선언

  • C언어에서 구조체는 struct 키워드를 사용하여 다음과 같이 정의합니다.
struct 구조체이름 {
    멤버변수1의타입 멤버변수1의이름;
    멤버변수2의타입 멤버변수2의이름;
    ...
};
  • 아래 그림에서 struct라는 키워드를 사용하여 구조체의 시작을 알리고, 구조체 이름인 book으로 구조체를 정의하고 있다.
  • 중괄호 사이에 char titile[30], char author[30], int price와 같은 변수들은 book의 멤버 변수들이다.
  • 마지막 세미콜론은 구조체 정의를 종료한다는 의미다.
  • 이렇게 정의된 book 구조체는 사용자 정의 자료형이라고 한다.

https://www.tcpschool.com/c/c_struct_intro

  • 이렇게 정의된 구조체 타입은 다음과 같이 구조체 변수로 선언하여 사용할 수 있다.
  • 또한, 구조체의 정의와 구조체 변수의 선언을 동시에 할 수도 있다.
struct 구조체이름 구조체변수이름;
struct 구조체이름 {
    멤버변수1의타입 멤버변수1의이름;
    멤버변수2의타입 멤버변수2의이름;
    ...
} 구조체변수이름;

typedef 키워드

  • C언어의 typedef 키워드는 이미 존재하는 타입에 새로운 이름을 붙일 때 사용한다.
  • 구조체 변수를 선언하거나 사용할 때에는 매번 struct 키워드를 사용하여 구조체임을 명시해야 한다. 하지만 typedef 키워드를 사용하여 구조체에 새로운 이름을 선언하면 매번 struct 키워드를 사용하지 않아도 된다.
  • typedef 키워드를 사용하여 새로운 이름을 선언하는 방법은 다음과 같으며, 또한 구조체의 정의와 typedef 선언을 동시에 할 수도 있다.
  • 참고로 구조체의 정의와 typedef 선언을 동시에 할 때에는 구조체의 이름을 생략할 수 있다.
typedef struct 구조체이름 구조체의새로운이름;
typedef struct (구조체이름) {
    멤버변수1의타입 멤버변수1의이름;
    멤버변수2의타입 멤버변수2의이름;
    ...
} 구조체의새로운이름;

구조체 멤버로의 접근 방법

  • 배열에서는 인덱스를 이용하여 배열 요소에 접근할 수 있다. 하지만 구조체에서 구조체 멤버로 접근하려고 할 때는 멤버 연산자(.)를 사용해야 한다.
  • 참고로 구조체의 주소값과 구조체의 첫 번째 멤버 변수의 주소값은 언제나 같다.
구조체변수이름.멤버변수이름

구조체 변수의 초기화

  • 구조체 변수를 초기화할 때에는 멤버 연산자(.)와 중괄호({})를 사용한다.
구조체변수이름 = {.멤버변수1이름 = 초깃값, .멤버변수2이름 = 초깃값, ...};
  • 위 방법을 사용하면 원하는 멤버 변수만을 초기화할 수 있다. 
  • 이때 멤버 변수가 정의된 순서와 초기화하는 순서는 아무런 상관이 없으며, 초기화하지 않은 멤버 변수는 0으로 초기화된다.
구조체변수이름 = {멤버변수1의초깃값, 멤버변수2의초깃값, ...};
  • 또한 배열의 초기화와 같은 방법으로 구조체 변수를 초기화할 수도 있다.
  • 이때 구조체 정의에서 멤버 변수가 정의된 순서에 따라 차례대로 초깃값이 설정되며, 나머지 멤버 변수는 0으로 초기화된다.
#include <stdio.h>  

struct book {
    char title[30];
    char author[30];
    int price;
};  

int main(void) {
    struct book my_book = {"HTML과 CSS", "홍길동", 28000};
    struct book java_book = {.title = "Java language", .price = 30000};  

    printf("첫 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
            my_book.title, my_book.author, my_book.price);

    printf("두 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
            java_book.title, java_book.author, java_book.price);

    return 0;
}

// 첫 번째 책의 제목은 HTML과 CSS이고, 저자는 홍길동이며, 가격은 28000원입니다.
// 두 번째 책의 제목은 Java language이고, 저자는 이며, 가격은 30000원입니다.
  • 위 예제는 앞서 살펴본 두 가지 방법을 사용하여 각각 구조체 변수를 초기화하는 예제다.
#include <stdio.h>  

typedef struct {
    char title[30];
    char author[30];
    int price;
}  TEXTBOOK;  

int main(void) {
    TEXTBOOK my_book = {"HTML과 CSS", "홍길동", 28000};
    TEXTBOOK java_book = {.title = "Java language", .price = 30000};  

    printf("첫 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
            my_book.title, my_book.author, my_book.price);

    printf("두 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
            java_book.title, java_book.author, java_book.price);

    return 0;
}

// 첫 번째 책의 제목은 HTML과 CSS이고, 저자는 홍길동이며, 가격은 28000원입니다.
// 두 번째 책의 제목은 Java language이고, 저자는 이며, 가격은 30000원입니다.
  • 위 예제는 앞선 예제의 구조체에 typedef 키워드를 사용하여 새로운 이름을 선언한 후 사용하는 예제다. 위 예제의 실행 결과처럼 typedef 키워드를 사용하여 구조체에 새로운 이름을 선언한 후 사용해도 structr 키워드를 그대로 사용한 것과 같은 결과를 얻을 수 있다.

 

구조체 배열 선언

  • C 언어에서 배열의 요소가 될 수 있는 타입에는 제한이 없으므로, 구조체 역시 배열의 한 요소가 될 수 있다.
  • 구조체 배열을 선언하는 방법은 다른 타입의 배열을 선언하는 방법과 같다. 또한 구조체 배열에서 각 배열 요소로 접근하는 방법도 일반 배열의 접근 방법과 완전히 같다.
struct book text_book[3] = {
    {"국어", "홍길동", 15000},
    {"영어", "이순신", 18000},
    {"수학1", "강감찬", 10000}
};  

puts("각 교과서의 이름은 다음과 같습니다.");
printf("%s, %s, %s\n", text_book[0].title, text_book[1].title, text_book[2].title);  

// 각 교과서의 이름은 다음과 같습니다.
// 국어, 영어, 수학1
  • 위의 예제처럼 구조체 배열은 2차원 배열의 초기화 방법과 똑같은 방법으로 초기화할 수 있으며, 멤버 연산자(.)를 사용하여 각 배열 요소의 멤버에 접근할 수 있다.

https://www.tcpschool.com/c/c_struct_pointer

 

구조체를 가리키는 포인터

  • 구조체 변수를 가리키는 구조체 포인터는 다음과 같이 선언한다.
struct 구조체이름* 구조체포인터이름;
  • 배열의 경우와는 달리 구조체의 이름은 구조체를 가리키는 주소가 아니다. 따라서 포인터에 할당할 때에는 반드시 주소 연산자(&)를 사용해야 한다.
  • 구조체 포인터를 이용하여 구조체의 멤버에 접근하는 방법에는 ① 참조 연산자(*)를 이용하는 방법과 ② 화살표 연산자(->)를 이용하는 방법이 있다.

참조 연산자를 이용하는 방법

  • 참조 연산자(*)는 멤버 연산자(.)보다 연산자 우선순위가 낮으므로 반드시 괄호(())를 사용해야 한다.
(*구조체포인터).멤버변수이름

화살표 연산자를 사용하는 방법

  • 화살표 연산자(->)의 앞쪽에는 구조체 포인터를, 뒤쪽에는 접근하고자 하는 구조체의 멤버 변수 이름을 사용하면 된다.
구조체포인터 -> 멤버변수이름
  • 위의 두 가지 방법은 완전히 같은 동작을 하며, 일반적으로 화살표 연산자가 좀 더 많이 사용된다.
struct book my_book = {"C언어 완전 해부", "홍길동", 35000};
struct book* ptr_my_book; // 구조체 포인트 선언  

ptr_my_book = &my_book;  

strcpy((*ptr_my_book).title, "C언어 마스터"); // 참조 연산자(*)를 이용하는 방법
strcpy(ptr_my_book->author, "이순신");        // 화살표 연산자(->)를 이용하는 방법
my_book.price = 32000;                        // 구조체 변수을 이용한 직접 수정  

printf("책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
        my_book.title, my_book.author, my_book.price);
        
// 책의 제목은 C언어 마스터이고, 저자는 이순신이며, 가격은 32000원입니다.

 

함수와 구조체

  • C언어에서는 함수를 호출할 때 전달되는 인수나, 함수가 종료될 때 반환되는 반환값으로 구조체를 사용할 수 있다. 그 방식은 기본 타입과 완전히 같으며, 구조체를 가리키는 포인터나 구조체의 한 멤버 변수만을 사용할 수도 있다.
typedef struct {
    int savings;
    int loan;
} PROP;  

int main(void) {
    int hong_prop;
    PROP hong = {10000000, 4000000};  

    hong_prop = calcProperty(hong.savings, hong.loan); // 구조체의 멤버 변수를 함수의 인수로 전달함  

    printf("홍길동의 재산은 적금 %d원에 대출 %d원을 제외한 총 %d원입니다.\n",
            hong.savings, hong.loan, hong_prop);
    return 0;
}

// 홍길동의 재산은 적금 10000000원에 대출 4000000원을 제외한 총 6000000원입니다.
  • 위와 같이 구조체를 인수로 전달하는 방식은 함수가 원본 구조체의 복사본을 가지고 작업하므로 안전하다는 장점을 가진다.
int hong_prop;
PROP hong = {10000000, 4000000};  

hong_prop = calcProperty(&hong); // 구조체의 주소를 함수의 인수로 전달함.  
printf("홍길동의 재산은 적금 %d원에 대출 %d원을 제외한 총 %d원입니다.\n", hong.savings, hong.loan, hong_prop);

// 홍길동의 재산은 적금 100원에 대출 4000000원을 제외한 총 -3999900원입니다.
  • 위와 같이 구조체 포인터를 인수로 전달하는 방식은 구조체의 복사본이 아닌 주소 하나만을 전달하므로 처리가 빠르다. 하지만 호출된 함수에서 원본 구조체에 직접 접근하므로 원본 데이터의 보호 측면에서는 매우 위험하다.
PROP prop;
int hong_prop;  

prop = initProperty();
hong_prop = calcProperty(&prop);  

printf("홍길동의 재산은 적금 %d원에 대출 %d원을 제외한 총 %d원입니다.\n", prop.savings, prop.loan, hong_prop);

// 홍길동의 재산은 적금 10000000원에 대출 4000000원을 제외한 총 6000000원입니다.
  • 따라서 위 예제의 calcProperty() 함수처럼 const 키워드를 사용하여 함수에 전달된 인수를 함수 내에서는 직접 수정할 수 없도록 하는 것이 좋다.
  • 위의 예제에서 initProperty() 함수는 반환값으로 구조체를 직접 반환한다. 기본적으로 C언어의 함수는 한 번에 하나의 데이터만을 반환할 수 있다. 하지만 이렇게 구조체를 사용하면 여러 개의 데이터를 한 번에 반환할 수 있다.

 

중첩된 구조체

  • C언어에서는 구조체를 정의할 때 멤버 변수로 또 다른 구조체를 포함할 수 있다.
struct name {
    char first[30];
    char last[30];
};  

struct friends {
    struct name friend_name;
    char address[30];
    char job[30];
};  

int main(void) {
    struct friends hong =
    {
        { "길동", "홍" },
        "서울시 강남구 역삼동",
        "학생"
    };  

    printf("%s\n\n", hong.address);
    printf("%s%s에게,\n", hong.friend_name.last, hong.friend_name.first);
    printf("그동안 잘 지냈니? 아직 %s이지?\n", hong.job);

    puts("공부 잘 하고, 다음에 꼭 한번 보자.\n잘 지내^^");
    return 0;
}

// 서울시 강남구 역삼동

// 홍길동에게,
// 그동안 잘 지냈니? 아직 학생이지?
// 공부 잘 하고, 다음에 꼭 한번 보자.
// 잘 지내^^



구조체의 크기

  • 일반적으로 구조체의 크기는 멤버 변수들의 크기에 따라 결정된다. 하지만 구조체의 크기가 언제나 멤버 변수들의 크기 총합과 일치하는 것은 아니다.
typedef struct {
    char a;
    int b;
    double c;
} TYPESIZE;  

int main(void) {
    puts("구조체 TypeSize의 각 멤버의 크기는 다음과 같습니다.");
    printf("%d %d %d\n", sizeof(char), sizeof(int), sizeof(double));  

    puts("구조체 TypeSize의 크기는 다음과 같습니다.");
    printf("%d\n", sizeof(TYPESIZE));

    return 0;
}

// 구조체 TYPESIZE의 각 멤버의 크기는 다음과 같습니다.
// 1 4 8
// 구조체 TYPESIZE의 크기는 다음과 같습니다.
// 16
  • 위의 예제에서 구조체 멤버 변수의 크기는 각각 1, 4, 8바이트지만 구조체의 크기는 멤버 변수들의 크기 총합인 13바이트가 아니라 16바이트가 된다.

 

바이트 패딩(byte padding)

  • 구조체를 메모리에 할당할 때 컴파일러는 프로그램의 속도 향상을 위해 바이트 패딩(byte padding)이라는 규칙을 이용한다.
  • 구조체는 다양한 크기의 타입을 멤버 변수로 가질 수 있는 타입이다. 하지만 컴파일러는 메모리의 접근을 쉽게 하기 위해 크기가 가장 큰 멤버 변수를 기준으로 모든 멤버 변수의 메모리 크기를 맞추게 된다. 이것을 바이트 패딩이라고 하며, 이때 추가되는 바이트를 패딩 바이트(padding byte)라고 한다.

https://www.tcpschool.com/c/c_struct_application

  • 앞선 예제에서는 크기가 가장 큰 double형 타입의 크기인 8바이트가 기준이 된다. 맨 처음 char형 멤버 변수를 위해 8바이트가 할당되며, 할당되는 1바이트를 제외한 7바이트가 남게 된다. 그 다음 int형 멤버 변수는 남은 7바이트보다 작으므로, 그대로 7바이트 중 4바이트를 할당하고 3바이트가 남게 된다. 마지막 double형 멤버 변수는 8바이트인데 남은 공간은 3바이트뿐이므로 다시 8바이트를 할당받는다. 따라서 이 구조체의 크기는 총 16바이트가 되며, 그중에서 패딩 바이트(padding byte)는 3바이트가 된다.

 

공용체

  • 공용체(union)는 union 키워드를 사용하여 선언하며, 한 가지를 제외한 모든 면에서 구조체와 같다. 바로 모든 멤버 변수가 하나의 메모리 공간을 공유한다는 점만이 다르다.
  • 모든 멤버 변수가 같은 메모리를 공유하므로, 공용체는 한 번에 하나의 멤버 변수밖에 사용할 수 없다.

https://www.tcpschool.com/c/c_struct_unionEnum

  • 공용체는 순서가 규칙적이지 않고, 미리 알 수 없는 다양한 타입의 데이터를 저장할 수 있도록 설계된 타입이다. 이러한 공용체는 크기가 가장 큰 멤버 변수의 크기로 메모리를 할당받는다. 따라서 공용체 배열을 사용하면, 같은 크기로 구성된 배열 요소에 다양한 크기의 데이터를 저장할 수 있다.
typedef union {
    unsigned char a;
    unsigned short b;
    unsigned int c;
} SHAREDATA;

int main(void) {
    SHAREDATA var;
    var.c = 0x12345678;  

    printf("%x\n", var.a);
    printf("%x\n", var.b);
    printf("%x\n", var.c);

    return 0;
}

// 78
// 5678
// 12345678
  • 위 예제는 공용체의 멤버 변수를 단 하나만 초기화해도, 나머지 멤버 변수들이 모두 같은 데이터를 공유한다는 것을 보여주는 예제다.
  • 공용체에 저장된 값의 의미는 값을 저장할 때 공용체의 어떤 멤버 변수를 사용했는지에 따라 전혀 달리 해석된다. 따라서 공용체의 어떤 멤버 변수를 사용하여 저장했는지를 별도로 저장하여, 접근할 때에도 같은 멤버 변수를 사용해야 한다.

 

열거체

  • 열거체(enumerated types)는 새로운 타입을 선언하면서, 동시에 해당 타입이 가질 수 있는 정수형 상숫값도 같이 명시할 수 있는 타입이다. 이러한 열거체를 이용하면 프로그램의 가독성이 높아지고, 변수가 지니는 값에 의미를 부여할 수도 있게 된다.
  • C언어에서 열거체는 enum 키워드를 사용하여 선언한다.
enum Weather {SUNNY = 0, CLOUD = 10, RAIN = 20, SNOW = 30};  

int main(void) {
    enum Weather wt;  
    wt = SUNNY;  

    switch (wt) {
        case SUNNY:
            puts("오늘은 햇볕이 쨍쨍!");
            break;

        case CLOUD:
            puts("비가 올락말락하네요!");
            break;

        case RAIN:
            puts("비가 내려요.. 우산 챙기세요!");
            break;

        case SNOW:
            puts("오늘은 눈싸움하는 날!");
            break;

        default: puts("도대체 무슨 날씨인건가요!!!");
    }  
    puts("각각의 열거체에 해당하는 정수값은 다음과 같습니다.");
    printf("%d %d %d %d\n", SUNNY, CLOUD, RAIN, SNOW);
    return 0;
}

// 오늘은 햇볕이 쨍쨍!
// 각각의 열거체 멤버에 해당하는 정수값은 다음과 같습니다.
// 0 10 20 30
  • 위의 예제처럼 사용자가 별도로 각 멤버에 해당하는 상숫값을 명시할 수 있다. 이때 상숫값을 따로 명시하지 않으면 0부터 시작되며, 그 다음 멤버의 값은 바로 앞 멤버의 값보다 1만큼 증가되며 정의된다.
enum Days {MON, TUE, WED, THU, FRI, SAT, SUN};  

int main(void) {
    enum Days today;  
    today = SAT;  

    if (today >= SAT && today <= SUN) {
        puts("오늘은 주말이네요~ 주말에도 열심히 공부하는 여러분은 최고에요!");
    } else {
        printf("주말까지 %d일 남았어요~ 조금만 더 힘내자구요!", 5 - today);
    }

    puts("각각의 열거체에 해당하는 정수값은 다음과 같습니다.");
    printf("%d %d %d %d %d %d %d\n", MON, TUE, WED, THU, FRI, SAT, SUN);
    return 0;
}

// 오늘은 주말이네요~ 주말에도 열심히 공부하는 여러분은 최고에요!
// 각각의 열거체 멤버에 해당하는 정수값은 다음과 같습니다.
// 0 1 2 3 4 5 6

 

Reference

728x90
반응형