모두의 코드
씹어먹는 C 언어 - <16 - 3. 구조체와 친구들(공용체(union), 열거형(enum))>
이번 강좌에서는
구조체의 나머지 기능에 대한 이해
공용체(union) 에 대한 이해
열거형(enum) 에 대한 이해

안녕하세요 여러분! 또 오래간만 입니다. (저 아직 죽지 않고 살아있어요) 구조체를 향한 강좌도 끝을 향해 달려가고 있습니다. 물론 이번 강좌에서는 구조체만을 다루는 것이 아니라 C 언어에서 사용 비중이 그렇게 크지는 않지만 어쨋든 알기는 알아야 하는 기능들에 대해서 배워볼 차례 입니다. 다시말해, 큰 산들은 다 넘었고 이제 우리 앞에는 조그마한 언덕만이 남아 있을 뿐이라는 것이죠 :)
구조체 안의 구조체
/* 구조체 안의 구조체*/ #include <stdio.h> struct employee { int age; int salary; }; struct company { struct employee data; char name[10]; }; int main() { struct company Kim; Kim.data.age = 31; Kim.data.salary = 3000000; printf("Kim's age : %d \n", Kim.data.age); printf("Kim's salary : %d$/year \n", Kim.data.salary); return 0; }
성공적으로 컴파일 했다면
실행 결과
Kim's age : 31 Kim's salary : 3000000$/year
먼저 employee
구조체를 살펴 봅시다.
struct employee { int age; int salary; };
위 구조체에는 int
형의 age
와 salary
변수 두 개가 멤버로 되어 있습니다. 다음, company
구조체를 살펴보면
struct company { struct employee data; char name[10]; };
와 같이 또다른 구조체 변수를 멤버로 가짐을 볼 수 있습니다. 뭔가 이상하다는 느낌이 들 수 도 있는데, 사실 위와 같이 정의해도 되는 것은 당연한 일입니다. 왜냐하면 제가 이전에도 말했듯이 구조체는 사용자가 정의한 또다른 형(type) 이라고 보는 것이기 때문이죠.
구조체 역시 int
나 char
과 같은 하나의 형 입니다. 우리가 만든 형은 이름이 struct employee
라는 것이고, 그 중 data
라는 (구조체) 변수를 생성하였죠. 이는 int a
와 지극히 똑같은 작업 입니다.
struct company Kim;
위와 같이 company
구조체를 정의한 뒤, 'struct company' 형의 변수 Kim
을 정의하였습니다. 이제, Kim
의 멤버들에게 값을 대입해 보아야 겠죠.
Kim.data.age = 31;
일단 위 문장을 살펴 봅시다. '.' 연산자의 우선 순위는 왼쪽 부터 이므로 Kim.data
가 해석 된 후, (Kim.data).age
가 해석이 됩니다. 다시 말해 "Kim
의 data
멤버의 age
멤버" 로 생각 되는 것이지요. 따라서 위와 같이 "Kim 의 data
멤버의 age
멤버" 에 31 의 값을 넣었습니다. 마찬가지로 salary
에 30000 을 넣었습니다.
따라서 위와 같이 출력이 됩니다.
구조체를 리턴하는 함수
구조체는 말그대로 여러분이 창조하신 하나의 타입이기 때문에 int , char
등이 가능했던 모든 것들을 구조체는 그대로 할 수 있습니다. 역시 구조체 형을 리턴하는 함수도 가능하겠지요.
/* 구조체를 리턴하는 함수 */ #include <stdio.h> struct AA function(int j); struct AA { int i; }; int main() { struct AA a; a = function(10); printf("a.i : %d \n", a.i); return 0; } struct AA function(int j) { struct AA A; A.i = j; return A; }
성공적으로 컴파일 했다면
실행 결과
a.i : 10
먼저 AA
라는 구조체를 정의하였습니다. 편의상 멤버는 int
i 로 하나만 가진다고 합시다.
struct AA { int i; };
아래는 "struct AA" 형을 리턴하는 함수 function
입니다. 인자로는 int j
를 취합니다.
struct AA function(int j) { struct AA A; A.i = j; return A; }
말그래도 struct AA
형을 리턴하기 때문에 리턴하는 것 역시 struct AA
형의 것이 되야 합니다. 위 함수는 인자로 받는 j
값으로 A
의 i
멤버를 j
의 값으로 초기화 한 후 이를 그대로 리턴합니다.
struct AA a; a = function(10);
우리는 main
함수에서 struct AA
타입의 구조체 변수 a
를 정의하였습니다. 그렇다면 a = function(10);
을 통해 function(10)
이 리턴한 구조체의 대입이 일어나게 됩니다. function(10)
은 'i
멤버의 값이 10 인 구조체 변수' 를 리턴하므로 a
의 i
멤버 값은 10 이 됩니다.
구조체 변수의 정의 방법
우리는 여태까지 구조체 변수를 다음과 같이 정의하였습니다.
struct Anomynous Var1, Var2; // "struct Anomynous" 형의 변수 Var1 , Var2 를 정의한다.
그런데 구조체 변수를 정의하는 방법 중 아래와 같이 색다른 방법을 소개해 드립니다.
/* 구조체 변수를 정의하는 색다른 방법. 예제를 이렇게 길게 만든 이유는 소스를 읽으면서 구조체와 조금 더 친해지기 바래서 입니다. 소스를 찬찬히 분석해보세요 ^^ */ #include <stdio.h> char copy_str(char *dest, char *src); int Print_Obj_Status(struct obj OBJ); struct obj { char name[20]; int x, y; } Ball; int main() { Ball.x = 3; Ball.y = 4; copy_str(Ball.name, "RED BALL"); Print_Obj_Status(Ball); return 0; } int Print_Obj_Status(struct obj OBJ) { printf("Location of %s \n", OBJ.name); printf("( %d , %d ) \n", OBJ.x, OBJ.y); return 0; } char copy_str(char *dest, char *src) { while (*src) { *dest = *src; src++; dest++; } *dest = '\0'; return 1; }
성공적으로 컴파일 했다면
실행 결과
Location of RED BALL ( 3 , 4 )
와 같이 나옵니다.
struct obj { char name[20]; int x, y; } Ball;
저는 위와 같이 sturct obj
라는 구조체를 정의하였고 멤버는 위와 같습니다. 그런데, 맨 아래 Ball
은 무엇인가요? 이는 바로 그냥 sturct obj
형의 Ball
이란 구조체 변수를 정의하라는 뜻입니다. 사실 우리가 main
함수 내부에서
struct obj Ball;
이라고 써왔든 것과 다를 바가 없습니다. 그냥, 위와 같이 구조체 변수를 정의하는 방법도 있다는 것을 알려 드린 것입니다.
나머지 부분은 여러분이 스스로 분석해 보시기 바랍니다.
/* 멤버를 쉽게 초기화 하기*/ #include <stdio.h> int Print_Status(struct HUMAN human); struct HUMAN { int age; int height; int weight; int gender; }; int main() { struct HUMAN Adam = {31, 182, 75, 0}; struct HUMAN Eve = {27, 166, 48, 1}; Print_Status(Adam); Print_Status(Eve); } int Print_Status(struct HUMAN human) { if (human.gender == 0) { printf("MALE \n"); } else { printf("FEMALE \n"); } printf("AGE : %d / Height : %d / Weight : %d \n", human.age, human.height, human.weight); if (human.gender == 0 && human.height >= 180) { printf("HE IS A WINNER!! \n"); } else if (human.gender == 0 && human.height < 180) { printf("HE IS A LOSER!! \n"); } printf("------------------------------------------- \n"); return 0; }
성공적으로 컴파일 했다면
실행 결과
MALE AGE : 31 / Height : 182 / Weight : 75 HE IS A WINNER!! ------------------------------------------- FEMALE AGE : 27 / Height : 166 / Weight : 48 -------------------------------------------
위 예제도 역시 구조체의 잡다한 기능 중 하나를 보여주고 있습니다. 바로 멤버를 초기화 하는 방식 인데요, 우리가 이전까지 멤버를 초기화 해온 방법 보다 더 쉽게 할 수 있습니다.
struct HUMAN { int age; int height; int weight; int gender; };
HUMAN
구조체는 위와 같이 4 개의 int
형 멤버들을 가지고 있습니다.
struct HUMAN Adam = {31, 182, 75, 0}; struct HUMAN Eve = {27, 166, 48, 1};
그리고 main
내부에서 위와 같이 Adam
과 Eve
를 정의하였죠. 이 때, = {}
를 통해서 중괄호 내부의 정보들이 순차적으로 각 멤버에 대입되게 됩니다. 따라서 Adam
의 경우 age
에는 31 이, height
에는 182 가, weight
에는 75, gender
에는 0 이 들어가게 되죠.
이전 예제에서 배운 초기화 방식에서는 다음과 같이 해주면 됩니다.
struct HUMAN { int age; int height; int weight; int gender; } Adam = {31, 182, 75, 0}, Eve = {27, 166, 48, 1};
어때요? 간단하지요. 그렇다면 이전 예제의
struct obj { char name[20]; int x, y; };
의 경우 어떻게 하면 될까요? 간단 합니다.
struct obj { char name[20]; int x, y; } Ball = {"abc", 10, 2};
와 같이 하게 되면 name
에는 "abc" , x
에는 10, y
에는 2 가 들어가게 됩니다.
여기서 구조체에 관한 이야기는 끝이 납니다. 아. 정말로 길었던 구조체 강좌였습니다. 아마 3 번의 강좌를 걸쳐서 제가 구조체에 관한 모든 지식들을 전달한 것 같습니다.
공용체 (union)
사실 공용체(union) 는 그다지 많이 사용하는 기능은 아닙니다만, 그래도 C 언어에서 제공하는 것들 중 하나이니 간단하게 나마 집고 넘어가도록 합시다. 공용체는 구조체와는 달리 메모리를 '공유' 합니다. 이게 도대체 무슨 말인가 하면 아래 그림을 참조해주세요.

위 그림을 보아도 알 수 있듯이 공용체의 각 멤버들의메모리 시작 주소는 모두 동일합니다. 따라서 우리는 위 그림의 union A
의 경우 j
의 값을 변경함으로 써 i
의 값을 변경할 수 있고 마찬가지로 i
의 값을 변경함으로써 j
의 값을 변경할 수 있게 됩니다. 과연 이 말이 진짜 인지 확인해 보도록 합시다.
/* 공용체 */ #include <stdio.h> union A { int i; char j; }; int main() { union A a; a.i = 0x12345678; printf("%x", a.j); return 0; }
성공적으로 컴파일 했다면
실행 결과
78
음. 과연 우리는 a
의 j
멤버 값을 전혀 설정해 주지 않았음에도 불구하고 i
에 0x12345678
을 대입하자 j
의 값이 78 로 잘 나왔습니다. 그런데 이상한 점이 듭니다. 왜 78 이 나왔을 까요? 0x12
가 나와야 되는 것 아닌가요? 분명히 i
와 j
에 동일한 주소값에 위치해 있고 i
가 0x12345678
로 메모리 상에 있다면 j
는 처음 두 개인 0x12
가 되어야 되는 것 아닌가요? 물론, 여러분의 생각은 옳습니다. 하지만 컴퓨터에서는 수를 이렇게 보관하지 않습니다. 적어도 여러분의 컴퓨터에서는요.
빅 엔디안 (Big Endian), 리틀 엔디안 (Little Endian)
컴퓨터에서 메모리에 수를 저장할 때, 우리가 생각하는 방법, 즉 낮은 주소값에 상위 비트를 적는 방식을 빅 엔디안 방식이라고 합니다. 그리고, 우리가 생각하는 방법의 정반대로 높은 주소값에 상위 비트를 적는 방식을 리틀 엔디안 이라고 합니다. 현재 대부분은 x86
프로세서는 리틀 엔디안 방식을 사용하고 있고 일부 컴퓨터에서만 빅 엔디안 방식을 사용하고 있습니다.
먼저, 빅 엔디안 에서 수를 어떻게 저장하는지 보면

와 같이 상식적으로 수를 저장하게 됩니다. 하지만 이건 빅 엔디안 방식의 경우이고요, 우리가 대부분 사용하는 프로세서는 리틀 엔디안 방식이므로 리틀 엔디안 방식의 경우를 살펴 보게 되면

와 같이 1 바이트 씩 역으로 보관함을 알 수 있습니다. 따라서 우리가 출력했던 j
값은 0x78
이 됩니다. 만일 j
를 short
형을 지정했으면 어떨까요? 0x7856
이 나올까요? 0x5678
이 나올까요?
#include <stdio.h> union A { int i; short j; }; int main() { union A a; a.i = 0x12345678; printf("%x", a.j); return 0; }
성공적으로 컴파일 했다면
실행 결과
5678
로 0x5678
이 나오게 됩니다.
이제 다시 머리가 혼란스러워 지기 시작했습니다. 메모리 상에 그대로 살펴 보게 되면 분명히 j
에 해당하는 부분은 0x7856
으로 출력되야 정상이지만 컴퓨터는 '지극히 정상적으로' 0x5678
을 출력하였습니다. 도대체 왜 그럴까요. 그 이유는 간단합니다.

이번에는 j
가 short
형 (2 바이트) 이므로 위와 같이 j
가 2 바이트를 차지하게 됩니다. 이 때 왜 j
의 값을 출력하면 0x7856
이 나오지 않고 0x5678
이 나올까? 이 문제에 대해 고민하고 있을 여러분을 위해 질문을 하나 던지겠습니다. i
의 값을 출력하면 얼마가 나올까요? 당연히 0x12345678
이 될것입니다. 왜냐하면 컴퓨터는 자신이 메모리에 수를 '리틀 엔디안 방식' 을 저장하고 있다는 사실을 알고 있기 때문이죠. 따라서 이를 출력할 때에는 적절한 변환을 취해서 0x12345678
을 출력할 것입니다.
j
의 경우도 마찬가지 입니다. j
는 현재 '78 56' 부분을 가리키고 있지만 컴퓨터는 j
가 리틀 엔디안 형식으로 이루어 졌다는 것을 알기 때문에 j
를 출력할 때에는 적절히 변환하여 0x5678
을 출력하게 될 것입니다.
어때요? 간단 하지요?
공용체에 관한 설명은 여기서 끝납니다. 아마도 공용체를 접할 가능성이 100 번 코딩 하다 보면 1 번 나올까 말까 한데 이를 자세히 짚고 넘어가는 것은 큰 의미가 없다고 생각합니다. 사실 공용체에 대해 배운 것 보다는 엔디안에 대해 배운 것이 훨씬 중요하기 때문에 혹여라도 엔디안에 대해 잊는 일은 없길 바랍니다.
열거형 (Enum)
프로그래밍을 하다 보면 각 데이터에 수를 대응 시키는 경우가 많습니다. 예를 들어 사람을 처리할 때, 남자에는 0, 여자에는 1 을 대응시켜서 처리하거나 색깔을 나타낼 때도 빨강에는 0, 흰색에는 1 등을 대응 시켜서 나타내게 됩니다. 이렇게 수를 대응 시켜서 처리할 때 에는 아래와 같이 헷갈리는 경우가 발생합니다.
if (human.gender == 0) // 사람의 성별이 0 일 때
남자에는 0, 여자에는 1 임을 확실하게 기억하고 있다면 상관이 없겠지만 기억하지 못하게 된다면 성에 대해 무엇을 대응 시켰는지 다시 찾아 보아야 된다는 번거로운 일이 발생합니다. 하지만 아래와 같이
if (human.gender == MALE) // 사람의 성별이 남자 일 때
와 같이 한다면 확실히 알아 듣기 쉽겠지요. 하지만 문제는 이를 위해 MALE
이라는 상수를 설정해야 되고 이 때문에 메모리가 낭비되게 됩니다. 이는 프로그래머의 입장에서 난감한 일이 아닐 수 없지요. C 에서는 이를 열거형(Enum)을 도입해서 말끔하게 해결해줍니다.
/* 열거형의 도입 */ #include <stdio.h> enum { RED, BLUE, WHITE, BLACK }; int main() { int palette = RED; switch (palette) { case RED: printf("palette : RED \n"); break; case BLUE: printf("palette : BLUE \n"); break; case WHITE: printf("palette : WHITE \n"); break; case BLACK: printf("palette : BLACK \n"); break; } }
성공적으로 컴파일 했다면
실행 결과
palette : RED
일단, 열거형을 정의한 부분 부터 살펴 봅시다.
enum { RED, BLUE, WHITE, BLACK };
열거형을 나타내기 위해서는 enum
을 쓰고 중괄호 안에 각각에 대해 써주면 됩니다. 그렇다면 컴파일러는 열거형에 나타나 있는 각 원소에 0 부터 차례로 정수값을 매겨 주게 됩니다. 즉 RED = 0, BLUE = 1, .. BLACK = 3
와 같이 말이지요. 이제 우리는 이를 자유롭게 이용하면 됩니다.
예를 들어
if (palette == 0) // 현재 파레트의 색이 빨강인지 확인한다.
로 했던 것을
if (palette == RED) // 현재 파레트의 색이 빨강인지 확인한다.
로 하면 됩니다. 사실 위와 의미는 정확히 똑같지만 프로그래머가 읽을 때 에는 큰 차이가 있게 되죠. 위와 같이 한다고 해서 실질적으로 RED
라는 변수가 메모리에 정해지는 것은 아닙니다. 컴파일 시에 컴파일러는 RED
는 모두 0 로 바꾸고 BLUE
는 모두 1 로 바꾸는 등 변환 작업을 하게 됩니다.
/* 열거형 팁 */ #include <stdio.h> enum { RED = 3, BLUE, WHITE, BLACK }; int main() { int palette = BLACK; printf("%d \n", palette); }
성공적으로 컴파일 했다면
실행 결과
6
열거형에서 처음 수를 0 으로 시작하기 싫다면 어떨까요. 단순히 원하는 수로 해주면 됩니다. 예를 들어 위와 같이
enum { RED = 3, BLUE, WHITE, BLACK };
으로 한다면 RED = 3
부터 해서 BLUE = 4, WHITE = 5, BLACK = 6
이 됩니다. 또한,
enum { RED = 3, BLUE, WHITE = 3, BLACK }
으로 한다면 수를 지정한 부분 부터 다시 시작 되는 방식으로 BLUE = 4, BLACK = 4
가 됩니다.
참고로 열거형 에서는 언제나 '정수값' 이여야만 합니다.
생각해볼 문제
문제 1
공용체는 도대체 어디에 써먹을 수 있을까요? 이 글을 읽어보세요.

댓글을 불러오는 중입니다..