Skip to Content
독학사독학사 2단계C프로그래밍13. 구조체·공용체·열거형

이번 문서의 목표: 이 파일을 다 읽으면 구조체·공용체·열거형을 정의하고 멤버에 접근하는 코드를 작성할 수 있고, 구조체와 공용체의 메모리 배치가 왜, 어떻게 다른지 바이트 단위로 그려 설명할 수 있다.

왜 이 주제가 필요한가

지금까지는 정수·실수·문자 같은 하나의 자료형만 다루는 변수를 써왔다. 하지만 실제 프로그램에서는 “학생 한 명”처럼 이름(문자열)·학번(정수)·평점(실수)이 한 덩어리로 묶인 데이터를 다뤄야 하는 경우가 훨씬 많다. C는 이렇게 서로 다른 자료형을 하나의 이름으로 묶는 도구로 구조체를 제공하고, 여러 자료형이 같은 메모리 공간을 번갈아 쓰게 하는 공용체, 그리고 이름 붙은 정수 상수의 집합을 만드는 열거형을 제공한다. 이 세 가지는 18편의 구조체·포인터·파일 통합 문제, 25편의 기출 유형에서 그대로 다시 등장하는 핵심 개념이므로 메모리 배치까지 정확히 이해해야 한다.

구조체: 서로 다른 자료형을 한 덩어리로

쉽게 말하면: 구조체는 여러 자료형의 변수를 종이 한 장에 적어 하나의 서류로 묶는 것과 같다.

구조체(struct, structure)는 서로 다른 자료형의 변수들을 하나의 이름 아래 묶어 새로운 자료형처럼 사용하게 해주는 문법이다. 구조체 안에 들어가는 각 변수를 멤버(member, 구성원)라고 부른다.

#include <stdio.h> #include <string.h> struct 학생 { char 이름[20]; int 학번; double 평점; }; int main(void) { struct 학생 s1; strcpy(s1.이름, "김민준"); s1.학번 = 20250001; s1.평점 = 3.8; printf("%s, %d, %.1f\n", s1.이름, s1.학번, s1.평점); return 0; }
김민준, 20250001, 3.8

struct 학생이 새로운 자료형의 이름이고, s1이 그 자료형의 변수(구조체 변수, 인스턴스)다. 멤버에 접근할 때는 점 연산자(., dot operator)를 쓴다. s1.이름은 “s1이라는 구조체 변수의 이름 멤버”라는 뜻이다.

구조체의 메모리 배치

구조체 변수를 선언하면, 멤버들이 선언한 순서대로 메모리에 나란히 배치된다. 다만 각 자료형이 요구하는 정렬(alignment, 특정 자료형이 특정 배수의 주소에서 시작해야 하는 하드웨어 제약) 때문에 멤버 사이에 빈 공간(패딩, padding)이 끼어들 수 있다.

struct 예제 { char c; /* 1바이트 */ int i; /* 4바이트 */ char c2; /* 1바이트 */ };

일반적인 64비트 환경에서 int는 4바이트 배수 주소에서 시작해야 하므로, 다음과 같이 배치된다.

오프셋(바이트)01234567891011
내용c패딩패딩패딩i 1바이트i 2바이트i 3바이트i 4바이트c2패딩패딩패딩

c는 1바이트만 쓰지만 바로 뒤에 3바이트의 패딩이 끼어 i가 4바이트 경계(오프셋 4)에서 시작하게 만든다. 마지막 c2도 구조체 전체 크기를 4의 배수로 맞추기 위해 뒤에 패딩이 붙는다. 따라서 sizeof(struct 예제)1 + 4 + 1 = 6이 아니라 12가 나온다.

시험 함정: “구조체의 크기는 항상 멤버 크기의 합과 같다”는 설명은 옳지 않은 것 고르기 문제의 정답(틀린 문장)으로 잘 나온다. 패딩 때문에 구조체 크기는 멤버 크기 합보다 크거나 같을 뿐, 항상 같지는 않다. 멤버 선언 순서를 크기가 큰 자료형부터 배치하면 패딩을 줄일 수 있다는 점도 함께 기억해 둔다.

구조체 배열과 구조체 포인터

구조체도 배열로 여러 개를 만들 수 있고, 포인터로 가리킬 수도 있다. 구조체 포인터를 통해 멤버에 접근할 때는 화살표 연산자(->, arrow operator)를 쓴다. p->멤버(*p).멤버와 완전히 같은 뜻이지만 훨씬 짧게 쓸 수 있어 실전에서 거의 항상 화살표 연산자를 쓴다.

#include <stdio.h> struct 점 { int x; int y; }; int main(void) { structpts[2] = {{1, 2}, {3, 4}}; struct*p = &pts[0]; printf("%d, %d\n", p->x, p->y); p++; printf("%d, %d\n", p->x, p->y); return 0; }
1, 2 3, 4

p는 처음에 pts[0]의 주소를 가리킨다. p++는 12편에서 배운 포인터 연산 규칙에 따라 p가 가리키는 자료형인 struct 점 크기(8바이트)만큼 주소를 이동시켜, pts[1]을 가리키게 만든다. 배열의 원소가 int가 아니라 구조체일 뿐, 포인터 연산 원리는 배열-포인터 관계와 완전히 같다.

공용체: 메모리를 공유하는 멤버들

쉽게 말하면: 공용체는 여러 자료형이 하나의 사물함을 시간차를 두고 나눠 쓰는 것과 같다 — 동시에 두 개를 넣을 수 없다.

공용체(union)는 구조체와 문법이 거의 같지만, 결정적인 차이가 하나 있다. 구조체는 멤버마다 독립된 메모리 공간을 갖지만, 공용체는 모든 멤버가 하나의 메모리 공간을 공유한다. 즉 한 번에 멤버 하나의 값만 유효하며, 새 멤버에 값을 쓰면 이전 멤버의 값은 덮어써진다.

#include <stdio.h> union 데이터 { int i; float f; char c[4]; }; int main(void) { union 데이터 d; printf("공용체 크기: %zu\n", sizeof(d)); d.i = 65; printf("i로 쓴 후 i = %d\n", d.i); d.f = 3.14f; printf("f로 덮어쓴 후 i = %d (의미 없는 값)\n", d.i); printf("f로 덮어쓴 후 f = %.2f\n", d.f); return 0; }
공용체 크기: 4 i로 쓴 후 i = 65 f로 덮어쓴 후 i = -1067869798 (의미 없는 값) f로 덮어쓴 후 f = 3.14

공용체의 메모리 배치: 공용체의 크기는 멤버 중 가장 큰 자료형의 크기로 정해진다. 위 예시에서 int(4바이트), float(4바이트), char[4](4바이트)가 모두 같은 크기라 공용체 크기는 4바이트다. 세 멤버는 모두 같은 시작 주소(오프셋 0) 에서 시작해 같은 4바이트를 공유한다.

d.i = 65;로 4바이트에 정수 65의 비트 패턴을 써 넣은 뒤, d.f = 3.14f;를 실행하면 같은 4바이트에 3.14를 나타내는 부동소수점 비트 패턴이 덮어써진다. 이 시점에서 d.i를 읽으면, 그 4바이트를 정수로 “잘못 해석”한 의미 없는 값이 나온다. 공용체는 “마지막에 쓴 멤버”만 유효하다는 점이 구조체와의 근본적인 차이다.

비교 항목구조체(struct)공용체(union)
메모리 배치멤버마다 독립된 공간(순차 배치, 패딩 포함)모든 멤버가 같은 공간을 공유(오프셋 0부터)
전체 크기멤버 크기의 합에 가까움(패딩 포함)가장 큰 멤버의 크기
동시 유효한 멤버 수모든 멤버가 동시에 유효마지막에 쓴 멤버 하나만 유효
대표 용도서로 다른 정보를 하나로 묶어 관리같은 메모리를 여러 방식으로 해석(메모리 절약)

시험 함정: “공용체의 크기는 모든 멤버 크기의 합이다”는 설명은 구조체와 혼동한 오답이다. 공용체 크기는 가장 큰 멤버의 크기(정렬 요구에 따라 그보다 약간 커질 수는 있음)이지, 합이 아니다.

열거형: 이름 붙은 정수 상수의 집합

쉽게 말하면: 열거형은 0, 1, 2 같은 숫자 대신 기억하기 쉬운 이름을 붙여 쓰는 상수 모음이다.

열거형(enum, enumeration)은 관련된 정수 상수들에 의미 있는 이름을 붙여 하나의 자료형처럼 묶는 문법이다. #define으로 매크로 상수를 여러 개 나열하는 것보다 관련된 상수들을 한눈에 묶어 보여주고, 컴파일러가 자료형 검사를 일부 도와준다는 장점이 있다.

#include <stdio.h> enum 요일 { 월요일, 화요일, 수요일, 목요일, 금요일, 토요일, 일요일 }; int main(void) { enum 요일 오늘 = 수요일; printf("오늘 = %d\n", 오늘); enum 요일 내일 = 오늘 + 1; printf("내일 = %d\n", 내일); return 0; }
오늘 = 2 내일 = 3

열거형의 각 이름(열거자, enumerator)은 0부터 시작하는 정수로 자동 할당된다. 월요일은 0, 화요일은 1, …, 수요일은 2가 된다. 열거형 변수는 내부적으로 int와 같은 정수로 취급되므로, 산술 연산(오늘 + 1)도 그대로 가능하다.

값을 직접 지정할 수도 있다. 지정하지 않은 이후 열거자는 바로 앞 값에서 1씩 증가한다.

enum 상태코드 { 성공 = 200, 잘못된요청 = 400, 서버오류 = 500, 게이트웨이오류 };

이 경우 성공은 200, 잘못된요청은 400, 서버오류는 500, 그리고 값을 지정하지 않은 게이트웨이오류는 바로 앞 서버오류(500)에서 1 증가한 501이 된다.

시험 함정: “열거형에 값을 하나도 지정하지 않으면 모두 0이 된다”는 설명은 옳지 않은 것 고르기 문제의 오답 후보로 자주 나온다. 실제로는 첫 번째만 0이고, 이후는 순서대로 1씩 증가한다. 또한 “일부만 지정하면 나머지는 다시 0부터 시작한다”도 틀린 설명이다 — 지정하지 않은 열거자는 항상 직전 값 + 1을 따른다.

세 문법의 관계 요약

자료형목적크기 결정대표 활용
구조체(struct)서로 다른 데이터를 한 덩어리로 묶기멤버 크기의 합(패딩 포함)학생 정보, 좌표, 레코드
공용체(union)같은 메모리를 여러 자료형으로 해석가장 큰 멤버의 크기메모리 절약, 자료형 변환 기법
열거형(enum)이름 붙은 정수 상수 집합int와 동일(보통 4바이트)상태값, 요일·색상 등 유한 집합

자주 틀리는 점

  • 구조체 멤버 접근에 점 연산자(.)를 써야 할 자리에 화살표 연산자(->)를 쓰거나 그 반대로 쓰는 실수가 잦다. 변수 자체는 ., 포인터는 ->가 규칙이다.
  • 구조체 크기를 멤버 크기의 단순 합으로 계산해 시험 문제를 틀리는 경우가 많다. 정렬로 인한 패딩을 반드시 고려해야 한다.
  • 공용체에 값을 쓴 뒤 다른 멤버로 읽으면 항상 오류가 난다고 착각하는데, 실제로는 컴파일 오류 없이 실행되며 다만 그 값이 의미 없는(정의되지 않은 해석의) 값이 된다는 점이 핵심이다.
  • 열거형 변수에 열거자 범위를 벗어난 정수를 대입해도 컴파일러가 막지 않는 경우가 많다는 점(열거형은 결국 int로 취급됨)을 놓쳐 오답을 고르는 경우가 있다.

핵심 정리

  • 구조체는 서로 다른 자료형의 멤버를 순차적으로 배치하며, 정렬 제약으로 인한 패딩 때문에 크기가 멤버 합보다 커질 수 있다.
  • 공용체는 모든 멤버가 같은 메모리 공간(오프셋 0부터, 가장 큰 멤버 크기만큼)을 공유하므로 한 번에 한 멤버만 유효하다.
  • 구조체 변수는 점 연산자(.), 구조체 포인터는 화살표 연산자(->)로 멤버에 접근한다.
  • 열거형은 이름 붙은 정수 상수 집합으로, 값을 지정하지 않으면 0부터 시작해 1씩 증가하고, 일부만 지정하면 나머지는 직전 값에서 1씩 증가한다.

마무리 복습

문제 14지선다
구조체(struct)와 공용체(union)의 메모리 배치 차이에 대한 설명으로 옳은 것은?
문제 24지선다
다음 구조체의 sizeof 값에 가장 큰 영향을 주는 요인은 무엇인가? struct S 는 char, int, char 순서로 멤버를 갖는다.
struct 점 { int x; int y; }; struct 점 pt = {1, 2}; struct*p = &pt;
문제 34지선다
위 코드에서 포인터 p가 가리키는 구조체의 멤버 x에 접근하는 방법에 대한 설명으로 옳은 것은?
union U { int i; float f; }; union U u;
문제 44지선다
위 공용체 변수 u에 대해 u.i에 값을 쓴 뒤 u.f를 읽으면 어떤 일이 일어나는가?
enum Color { RED, GREEN = 5, BLUE };
문제 54지선다
위 열거형 정의에서 BLUE의 값은 얼마인가?
문제 64지선다
열거형(enum)에 대한 설명으로 옳지 않은 것은?
문제 74지선다
구조체 배열 struct Point pts[3]에서 pts[1]의 멤버 y에 접근하는 올바른 표현은?

참고 자료

  • C프로그래밍 강의 노트 – 구조체·공용체·열거형 및 파일 입출력 성격의 대학 강의 자료 — 구조체 메모리 배치와 공용체 활용 예제 설명에 참고
  • 국가평생교육진흥원 독학학위제: https://bdes.nile.or.kr 
Last updated on