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 구조체는 사용자 정의 자료형이라고 한다.

- 이렇게 정의된 구조체 타입은 다음과 같이 구조체 변수로 선언하여 사용할 수 있다.
- 또한, 구조체의 정의와 구조체 변수의 선언을 동시에 할 수도 있다.
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차원 배열의 초기화 방법과 똑같은 방법으로 초기화할 수 있으며, 멤버 연산자(.)를 사용하여 각 배열 요소의 멤버에 접근할 수 있다.

구조체를 가리키는 포인터
- 구조체 변수를 가리키는 구조체 포인터는 다음과 같이 선언한다.
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)라고 한다.

- 앞선 예제에서는 크기가 가장 큰 double형 타입의 크기인 8바이트가 기준이 된다. 맨 처음 char형 멤버 변수를 위해 8바이트가 할당되며, 할당되는 1바이트를 제외한 7바이트가 남게 된다. 그 다음 int형 멤버 변수는 남은 7바이트보다 작으므로, 그대로 7바이트 중 4바이트를 할당하고 3바이트가 남게 된다. 마지막 double형 멤버 변수는 8바이트인데 남은 공간은 3바이트뿐이므로 다시 8바이트를 할당받는다. 따라서 이 구조체의 크기는 총 16바이트가 되며, 그중에서 패딩 바이트(padding byte)는 3바이트가 된다.
공용체
- 공용체(union)는 union 키워드를 사용하여 선언하며, 한 가지를 제외한 모든 면에서 구조체와 같다. 바로 모든 멤버 변수가 하나의 메모리 공간을 공유한다는 점만이 다르다.
- 모든 멤버 변수가 같은 메모리를 공유하므로, 공용체는 한 번에 하나의 멤버 변수밖에 사용할 수 없다.


- 공용체는 순서가 규칙적이지 않고, 미리 알 수 없는 다양한 타입의 데이터를 저장할 수 있도록 설계된 타입이다. 이러한 공용체는 크기가 가장 큰 멤버 변수의 크기로 메모리를 할당받는다. 따라서 공용체 배열을 사용하면, 같은 크기로 구성된 배열 요소에 다양한 크기의 데이터를 저장할 수 있다.
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
반응형
'Language > C' 카테고리의 다른 글
| [C] C언어 선행처리 - 선행처리기, 매크로 함수 (1) | 2026.06.09 |
|---|---|
| [C] C언어 입력과 출력 - 콘솔, 파일, 함수 (1) | 2026.06.08 |
| [C] C언어 문자와 문자열 - 입출력, 함수 (0) | 2026.06.07 |
| [C] C언어 메모리 관리 - 구조, 스택 프레임, 동적 할당 (0) | 2026.06.07 |
| [C] C언어 포인터와 배열 - 관계, 포인터 배열과 배열 포인터 (0) | 2026.06.07 |