C 언어 | 구조체 선언 | 형식의 별명 - typedef
typedef 지정자를 이용한 형식의 별명을 정의하는 방법을 소개한다. 이에 따라 기존의 자료형에 대해서 간단한 이름을 주는 곳이 있으며, 복잡한 구조체와 포인터 형의 존재를 은폐할 수 있다.
고유한 유형
C 언어에서는 개발자가 새로운 고유한 형식을 선언하는 수단이 존재한다. 정확하게는 기존의 자료형에 대해 다른 별명을 준다는 것이다. 이미 존재하는 자료형에 다른 이름을 붙이는 것은 무의미하다고 느낄지도 모른다. 그러나 사용법에 따라서는 코드를 단순하고 변경에 강한 유연하고 보수성이 높은 프로그램을 제공한다.
C 언어는 모든 시스템에서 구현되는 국제적인 프로그래밍 언어이다. 특히 표준화된 함수만을 사용하는 것이라면 이론적으로는 이식할 필요 없이 다른 시스템에서도 컴파일할 수 있어야 한다. 그러나 다른 시스템은 32비트 컴퓨터도 모르고, 64비트 컴퓨터일지도 모른다. 이러한 다른 시스템에 이식 작업을 최소화하는 방법으로 자료형의 별명을 사용할 수 있다.
예를 들어, 프로그램 코드는 int 형이 32비트인 것으로 가정되므로써, 이를 16비트 컴퓨터에서 실행해도 올바른 동작은 기대할 수 없다. 그래서 32비트 정수를 DWORD, 16비트 정수를 WORD 형으로 소스를 통일하면 다른 시스템에 이식도 간단한다. 32비트 컴퓨터에서는 DWORD 형을 int 형의 별명으로 하고, WORD 형을 short int 형의 별명으로 정의하면 실체가 추상화된다.
이것을 다른 시스템에 이식하는 경우, 해당 시스템에서 32비트와 정의되는 자료형에 DWORD으로하고, 16비트와 정의되는 자료형에 WORD라는 별칭을 지정한다. 그러면 나머지 소스는 전혀 변경할 필요가 없어지는 것이다. 이것은 이식성 향상에 중요한 기술이 될 것이다.
기존의 형태에 별명을 붙일 때에는 typedef 지정자를 사용한다. typedef에 의해 형식에 대한 새로운 식별자를 정의할 수 있다. 새로운 유형 식별자를 정의한 후 기존 형식을 사용할 수 없게 되는 것은 아니다.
typedef 지정자
typedef 기존의형명 새로운 형명;
기존의 형명은 int와 char * 같은 이미 존재하는 형태의 이름을 지정한다. 새로운 형명에 아직 존재하지 않는 형태의 식별자를 지정한다. 예를 들어 unsigned char의 별명은 다음과 같이 정의할 수 있다.
typedef unsigned char BYTE;
이 선언은 unsigned char 형의 별명으로 BYTE 형을 정하고 있다. typedef 키워드는 형식을 선언할 수있는 곳이면 지정할 수 있지만, 별명은 typedef 키워드보다도 이후에야 사용할 수 없기 때문에, 일반적으로 함수보다 이전 소스의 맨 위에 한다. 기존의 형명은 이미 존재하는 형식이면 무엇이든 상관 없다. 이전 typedef에 의해 정의된 형명도 지정할 수 있다.
코드1
#include <stdio.h>
typedef unsigned char BYTE;
typedef int DWORD;
struct Color {
BYTE r , g , b;
};
DWORD main() {
struct Color color = { 0xFF , 0xAA , 0xAA };
printf("R = %d : G = %d : B = %d\n" , color.r , color.g , color.b);
return 0;
}
이 프로그램에는 unsigned char 형의 별명 BYTE으로 하고, int 형의 별명을 DWORD를 정의하고 있다. Color 구조체와 main() 함수에는 이것들을 typedef 명을 사용하여 프로그램되어 있다. 그러나 BYTE형도 DWORD형도 그 실체는 C 언어 간단한 형인 unsigned char와 int이다. 이건 그냥 별명에 불과하다는 것을 잊지 말자.
포인터 형의 별명은 기존의 형명 후에 * 를 지정하면 한다. char 형 포인터의 별칭 STR을 작성하는 경우에
typedef char * STR;
위와 같이 작성한다. 또한 typedef의 새로운 형명 콤마 “,“로 구분하여 한번에 여러 별명을 줄 수 있다. 예를 들어 다음과 같이 작성하는 것으로, char 형의 별명과 char 형 포인터의 별명을 한번에 정의할 수 있다.
typedef char STR , *PSTR;
이것은 char형의 별명 STR과 char형 포인터의 별칭 PSTR가 새롭게 정의되고 있다. 일반적으로 문자열 포인터를 작성하는 경우는 char *
와 같이 쓴다고 생각하지만, 이렇게 포인터 형의 별명을 작성하면 PSTR을 지정하는 것만으로 포인터 변수를 선언할 수 있다.
코드2
#include <stdio.h>
typedef char STR , *PSTR;
int main() {
STR str[] = "Kitty on your lap";
PSTR pstr = str;
printf("%s\n" , pstr);
return 0;
}
코드 2는 char 형의 별명 STR과 char *
형의 별명으로 PSTR를 정의하고, 이를 main() 함수에서 실제 사용하고 있다. str[] 변수는 STR 형이므로 실체는 char 형의 배열이다. pstr 변수는 PSTR 형이므로 char *
의 변수라고 생각할 수 있다. 이와 같이 포인터에 별명을 구사하여 포인터의 존재를 어느 정도 은폐할 수 있다.
특히 typedef가 위력을 발휘하는 것은 구조체나 공용체의 별명을 만들 때이다. 단순형의 별명에 큰 메리트가 느껴지지 않을지도 모르지만, 구조체의 별명을 작성하면 소스의 가독성 향상과 생산성에 연결할 수 있다.
기존 구조체의 태그명에서 구조체의 인스턴스를 생성하는 작업은 귀찮은 일로 struct 키워드를 명시적으로 지정할 필요가 있었다. 이는 길고 번거로운 구문으로 대부분의 프로그래머는 이렇게 낭비인거 같은 struct나 union 키워드의 지정을 싫어한다. 그래서 typedef에 의해 구조체나 공용체에 새로운 형명을 할당하게 된다. 이렇게 하면 struct나 union 키워드를 지정하지 않고, 형식 이름만으로 인스턴스를 만들 수 있게 될 것이다.
struct tag_Point { int x , y; };
typedef struct tag_Point Point;
이처럼 구조체의 별명을 정의할 수 있다. 태그명으로 인스턴스를 만들려면 struct tag_Point
를 작성하지 않으면, 컴파일러는 인식을 하지 못했다. 그러나 struct tag_Point
형에 별명으로 Point를 지정하면 Point를 지정하는 것만으로 tag_Point 구조체의 인스턴스를 만들 수 있게 된다.
그러나 tag_Point은 여기서 밖에 사용하지 않는 태그명이므로 그다지 필요가 없다. 태그명이 그 후에 요구되지 않는다면, 무명 구조체에 별명을 주는 것이 효율적이다. 구조체 선언 시에 다음과 같은 구문을 이용하여 동시에 별명을 정의할 수 있기 때문에 무명 구조체에 별명을 줄 수도 있다.
typedef 지정자에 구조체 별명
typedef struct 태그명 { 멤버선언 } 새로운형명;
구조체 선언에서 태그명은 생략할 수 있기 때문에, 필요하지 않은 경우 무명 구조체를 선언하고 동시에 새로운 형명을 줄 수 있다. 많은 C 언어 프로그래머는 구조체에 별명을 부여하는 것을 선호하고, 구조체를 선언할 시에 typedef를 사용하여 별명을 부여한다.
코드3
#include <stdio.h>
typedef struct { int x , y; } Point;
int main() {
Point po = { 200 , 50 };
printf("X = %d : Y = %d\n" , po.x , po.y);
return 0;
}
코드3은 int 형의 멤버 x와 y를 가진 무명 구조체에 Point라는 별명을 부여하고 있다. 많은 프로그래머는 이렇게 태그명 대신에 typedef 명을 구조체로 지정한다. 이것이 struct 키워드를 지정할 필요가 없기 때문에 구조체 변수의 선언이 쉬워진다. 공용체에 별명을 붙이는 경우도 동일하다.
함수형과 별명
사실은 C 언어에서는 함수도 하나의 형으로 해석되고 있다. 함수의 형은 반환 값과 인수 목록으로 구성되어 있다. 하지만 함수 자체가 식별자(즉, 함수명)으로 판단되기 때문에, 평소에는 이를 의식하는 것은 아니다. 하지만 함수형이라는 것을 의식하면, 함수와 함수형의 인스턴스으로써 생각할 수 있는 것이다.
예를 들어, 다음과 같은 함수가 선언되는 경우를 생각해 보자.
int Function1(char * , float);
int Function2(char * , float);
이 경우 Function1() 함수와 Function2() 함수는 같은 함수형이라고 표현할 수 있다. 물론 이러한 함수의 정의에 상호 관계는 존재하지 않는다. 이 함수는 독립적이며 물리적 연결 같은건 아무것도 없다. 유일한 공통점은 형태가 같다는 것이다.
이처럼 함수를 형태라는 관점에서 바라 볼 수 있는 것이다. 그러면 typedef에 의해 함수형의 이름을 붙일 수 있다고 한다면 재미있는 발상이 떠오르는 것이다. 사실 이것은 구문적으로 가능해지고 있다. 다음과 같이 작성하여 함수형에 이름을 붙일 수 있는 것이다.
typedef 지정자에 의해 함수형의 별명
typedef 반환값 새로운형명(인수 목록);
이는 매우 흥미로운 시도이다. 함수형에 이름을 붙이면, 형명 및 함수명만으로 함수를 선언할 수 있다.
코드4
#include <stdio.h>
typedef void TEXTOUT(char *str);
TEXTOUT println;
int main() {
println("Kitty on your lap");
return 0;
}
void println(char *str) {
printf("%s\n" , str);
}
코드4의 println() 함수에 주목해 보자. 이 println() 함수는 TEXTOUT 형의 함수로 해석할 수 있는 것이다. TEXTOUT 형은 typedef 지정자로 만들었다. 반환값은 void으로 하고, 매개 변수가 char *
형의 함수형의 별명이다. TEXTOUT println이라는 함수 선언자는 void println (char *str)
라는 선언에 동일하다고 생각할 수 있다.