16. Structures, Unions, and Enumerations(2)
4. Unions
공용체(union)는 구조체처럼 하나 또는 그 이상의 멤버로 구성되어 있고 다른 자료형을 가질 수 있다. 그러나, 컴파일러는 가장 큰 멤버에 대해 충분한 공간을 제공하고, 이 공간 내부에서 멤버끼리 덮어 씌우면서(overlay) 사용된다. 결과적으로, 하나의 멤버에 새로운 값을 대입하는 것은 다른 멤버의 값또한 바꾼다.
공용체의 기본적인 속성을 설명하기 위해, 두 개의 멤버를 가지는 공용체 변수 u
를 선언해보자.
union {
int i;
double d;
} u;
어떻게 공용체의 선언이 구조체 선언과 비슷한지 주목하자.
struct {
int i;
double d;
} s;
사실, 구조체 s
와 공용체 u
는 한 가지 방식만 다르다. s
의 멤버는 메모리 내부에서 다른 주소에 저장된다는 점이고, u
의 멤버는 같은 주소에 저장된다는 점이다. 메모리 내부에서 s
와 u
는 아래처럼 보일 것이다(int
값이 4byte를 필요로 하고, double
이 8byte를 차지한다고 가정했을때).
s
구조체에서, i
와 d
는 다른 메모리 공간을 차지한다. s
의 총 크기는 12byte이다. u
공용체에서, i
와 d
는 겹쳐지는데(i
는 d
의 첫번째 4byte), 그래서 u
는 오직 8byte만을 차지한다. 또한, i
와 d
는 같은 주소를 가질 수 있다.
공용체의 멤버에는 구조체의 멤버와 같은 방식으로 접근할 수 있다. u
의 멤버 i
에 숫자 82를 저장하기 위해, 우리는 아래와 같이 작성할 수 있다.
u.i = 82;
멤버 d
에 값 44.8을 저장하기 위해, 아래처럼 작성할 수 있다.
u.d = 74.8;
컴파일러가 공용체어 멤버에 대한 공간을 겹치기 때문에, 하나의 멤버를 수정하는 것은 이전에 어떠한 다른 멤버에 저장외어있던 값을 수정한다. 그래서, 만약 u.d
에 값을 저장한다면 이전에 저장되었던 u.i
의 값이 사리질 것이다.(만약 u.i
의 값을 검사한다면, 아마 의미없는 값이 나올 것이다) 비슷하게, u.i
를 수정하는 것은 u.d
를 오염시킨다(corrupt). 이러한 속성때문에 우리는 u
가 i
와 d
둘다 저장하는 것이 아닌, i
와 d
중 하나를 저장하는 공간으로써 생각할 것이다(구조체 s
는 i
와 d
를 저장할 수 있도록 한다).
공용체의 특성은 대부분 구조체의 특성과 동일하다. 우리는 구조체 태그와 자료형을 선언하는 것과 동이한 방식으로 공용체 태그와 공용체 자료형을 선언할 수 있다. 구조체와 비슷하게, 공용체는 =
연산자를 사용하여 복사될 수 있으며, 함수로 전달될 수 있고, 함수에 의해 반환될 수 있다.
공용체는 구조체와 비슷한 방식으로 초기화 될 수 있다. 그러나 공용체의 첫번째 멤버에만 초기값을 부여할 수 있다. 예를 들어, 아래의 방식처럼 u
의 멤버 i
를 초기화할 수 있다.
union {
int i;
double d;
} u = {0};
중괄호가 필요하다는 것을 주목하자. 중괄호 내부의 표현식은 반드시 상수여야 한다.(이 규칙은 C99와 조금 다른데, 이는 Section 18.5에서 알아볼 것이다)
이전에 배열과 구조체의 맥락에서 알아보았던 C99의 특성인 designated initializer이 공용체에서도 사용 가능하다. designated initializer는 초기화되어야 하는 공용체의 멤버를 특정하도록 한다. 예를 들어 u
의 멤버 d
를 초기화 하기 위해 아래처럼 작성할 수 있다.
union {
int i;
double d;
} u = {.d = 10.0};
하나의 멤버만 초기화될 수 있지만, 이것이 꼭 첫번째 멤버일 필요는 없다.
공용체에 대한 몇가지 적용이 있다. 이 중 2개만 알아볼 것이고 나머지에 대한 적용은 Section 20.3에서 알아볼 것이다.
Using Unions to Save Space
우리는 구조체 내부에서 공간을 절약할 수 있는 방법으로 공용체를 사용한다. 우리가 선물 카탈로그를 통해 판매되는 항목에 대한 정보를 가진 구조체를 설계하고 있다고 생각해보자. 카탈로그는 오직 판매품목의 3종류만 있다. books, mugs, shirts이다. 각각의 항목은 잔여수량, 가격, 항목의 종류에 따른 다른 정보 등을 포함한다.
Books: Title, author, number of pages
Mugs: Design
Shirts: Design, colors available, sizes available
첫번째 설계는 아래와 같은 구조체의 형태를 보일 수도 있다.
struct catalog_item {
int stock_number;
double price;
int item_type;
char title[TITLE_LEN+1];
char author[AUTHOR_LEN+1];
int num_pages;
char design[DESIGN_LEN+1];
int colors;
int sizes;
};
item_type
멤버는 BOOK
, MUG
, SHIRT
중 하나의 값을 가진다. colors
와 sizes
멤버는 색과 크기를 결합한 코드를 저장한다.
이 구조체가 완벽하게 사용가능한다고 해도 공간을 낭비하는데, 왜냐하면 카탈로그 내부의 항목의 일부만 모든 상품에 공통적이고 나머지는 필요가 없기 때문이다. 예를 들어 book 항목에는 design
, colors
, sizes
는 필요하지 않다. catalog_item
구조체에 공용체를 넣는 것으로, 우리는 구조체가 필요로 하는 공간을 줄일 수 있다. 공용체의 멤버는 특정 카탈로그의 항목에 해당하는 데이터를 포함하는 구조체일 것이다.
struct catalog_item {
int stock_number;
double price;
int item_type;
union {
struct {
char title[TITLE_LEN+1];
char author[AUTHOR_LEN+1];
int num_pages;
} book;
struct {
char design[DESIGN_LEN+1];
} mug;
struct {
char design[DESIGN_LEN+1];
int colors;
int sizes;
} shirt;
} item;
};
(item
의 이름을 가진) 공용체가 catalog_item
의 멤버이고, book
, mug
, shirt
구조체는 item
의 멤버임에 주목하자. c
가 book을 표현하는 catalog_item
구조체라면, 우리는 book의 제목을 아래와 같은 방식으로 출력할 수 있다.
printf("%s", c.item.book.title);
이 예시가 보여주는 것처럼, 구조체 내부에 중첩된 공용체에 접근하는 것은 어색할 수 있다. 책의 이름을 위치시키기 위해, 우리는 구조체(c
)의 이름을 명시하고, 구조체(item
)의 멤버인 공용체의 이름을 명시하고, 공용체(book
)의 멤버인 구조체의 이름을 명시하고, 그리고 그 후 구조체(title
)의 멤버의 이름을 명시했다.
공용체의 흥미로운 측면을 설명하기 위해 catalog_item
구조체를 사용했었다. 일반적으로, 공용체의 멤버에 값을 저장하고 다른 멤버를 통해 그 데이터에 접근하는 것은 좋은 생각이 아니다. 왜냐하면 공용체의 멤버에 값을 대입하는 것은 다른 멤버의 값이 정의되지 않게(undefined) 하기 때문이다. 그러나 C 표준에서는 특별한 경우를 언급하는데, 2개 또는 3개 이상의 멤버를 가진 공용체의 멤버가 구조체이고, 그 구조체가 하나 이상 일치하는 멤버로 시작할 때이다. (이 멤버들은 같은 순서여야하고 호환 가능한 자료형을 가져야하지만, 같은 이름일 필요는 없다.) 만약 구조체 중 하나가 현재 유효하다면, 다른 구조체 내부의 일치하는 멤버도 또한 유효할 것이다.
catalog_item
구조체 내부에 내제된 공용체를 생각해보자. 멤버로써 3개의 구조체를 가지고, 그 중 2개는 (mug
와 shirt
) 일치하는 멤버(design
)로 시작한다. 이제, 우리가 design
멤버 중 하나에 값을 대입한다고 생각해보자.
strcpy(c.item.mug.design, "Cats");
다른 구조체 내부의 design
멤버는 정의될 것이고, 같은 값을 가질 것이다.
printf("%s", c.item.shirt.design); /* prints "Cats" */
Using Unions to Build Mixed Data Structures
공용체는 중요한 사용처가 또 있다. 다른 자료형의 데이터를 혼합한 것을 포함하는 데이터 구조를 만드는 것에 사용된다. 요소가 int
와 double
값이 혼합인 배열이 필요하다고 생각해보자. 배열의 요소는 반드시 같은 자료형이여야 하기 때문에 이러한 배열을 만드는 것은 불가능해보인다. 하지만 공용체를 사용한다면 이는 비교적 간단하다. 첫번째, 배열에 저장될 다른 종류의 데이터를 표현하는 멤버를 가진 공용체를 정의한다.
typedef union {
int i;
double d;
} Number;
다음으로, 요소가 Number
값인 배열을 만든다.
Number number_array[1000];
number_array
의 각 요소는 Number
공용체이다. Number
공용체는 int
와 double
값을 둘 다 저장할 수 있고, 이를 통해 number_array
내부에서 int
와 double
를 혼합하여 저장하는 것이 가능하다. 예를 들어 number_array
의 요소 0이 5를 저정하고, 요소 1이 8.395를 저장하도록 하기 위해 아래와 같이 작성할 수 있다.
number_array[0].i = 5;
number_array[1].d = 8.395;
Adding a "Tag Field" to a Union
공용체는 가장 큰 문제때문에 고통을 겪는다. 공용체의 멤버가 마지막으로 수정되어 의미있는 값을 가지고 있는지 쉽게 알아낼 방법이 없다는 것이다. Number
공용체에 저장된 현재 값을 출력하는 함수를 작성한다고 생각해보자. 이 함수는 대강 아래와 같은 모습을 보일 것이다.
void print_number(Number n)
{
if (n contains an integer)
printf("%d", n.i);
else
printf("%g", n.d);
}
불행하게도, print_number
는 n
이 정수를 가지고 있는지 부동소수점 숫자를 가지고있는지 결정할 방법이 없다.
이 정보를 추적하기 위해서, 우리는 다른 멤버를 가진 구조체에 공용체를 넣을 수 있다. 그 멤버는 공용체 내에 현재 저장된 것이 무엇인지 우리에게 알릴 수 있는 목적을 가진 "tag field" 또는 "discriminant"이다. catalog_item
구조체에서는, item_type
이 이러한 역할을 한다.
Number
자료형을 공용체가 포함된 구조체로 변환해보자.
#define INT_KIND 0
#deinfe DOUBLE_KIND 1
typedef struct {
int kind; /* tag field */
union {
int i;
double d;
} u;
} Number;
Number
는 kind
와 u
두 개의 멤버를 가진다. kind
의 값은 INT_KIND
와 DOUBLE_KIND
둘 다 될 수 있다.
u
의 멤버에 값을 대입할 때마다, 우리가 수정한 u
를 생각하면서 kind
또한 바꾸어야 할 것이다. 예를 들어 n
이 Number
변수이고, u
의 멤버인 i
에 대입을 한다면 아래의 모습을 보일 것이다.
n.kind = INT_KIND;
n.u.i = 82;
i
대입 과정에서, 우리가 n
의 멤버 u
를 선택하고, 그 후 u
의 멤버 i
에 대입해야 한다는 것을 주목하자.
우리가 Number
변수에 저장된 숫자를 가져올 필요가 있을 때, kind
는 공용체의 멤버가 마지막에 어떠한 값으로 수정되었는지 알려준다. print_number
함수는 이러한 능력을 이용하였다.
void print_number(Number n)
{
if (n.kind == INT_KIND)
printf("%d", n.u.i);
else
printf("%g", n.u.d);
}
공용체의 멤버에 대입이 이루어질 때마다 tag field를 수정해야 한다.
5. Enumerations
많은 프로그램에서, 의미있는 값을 가지는 몇 종류의 변수가 필요할 수도 있다. 예를 들어 Boolean 변수는 "true"와 "false" 오직 2종류의 값만을 가진다. 카드게임의 suit를 저장하는 변수는 "clubs", "diamonds", "hearts", "spades" 4종류를 가질 것이다. 이러한 변수를 처리하는 명확한 방법은 정수로써 선언하거나, 변수의 값을 표현하는 코드를 가지도록 하는 것이다.
int s; /* s will store a suit */
...
s = 2; /* 2 represents "hearts" */
이러한 기법으로 작업을 한다고 해도, 아쉬운점이 남는다. 누군가 프로그램을 읽을 때 s
가 오직 4개의 값을 가진다는 것을 말해줄 수 없기 때문이다. 그리고 2라는 숫자의 중요성이 즉각적으로 드러나지 않기 때문이다.
매크로를 사용하여 다양한 suit에 대한 suit "자료형"과 이름을 정의하는 것은 올바른 방향이다.
#define SUIT int
#define CLUBS 0
#define DIAMONDS 1
#define HEARTS 2
#define SPADES 3
위의 정의를 사용하여 예제를 더 가독성있게 만들 수 있다.
SUIT s;
...
s = HEARTS;
이 기법을 통해 개선이 되긴 하지만, 이것은 가장 좋은 해결책이 아니다. 프로그램을 읽는 사람에게 SUIT
에 관한 매크로가 "동일한 자료형"을 가진다는 것을 나타낼 수 있는 방법이 없다. SUIT
는 4종류이지만, 만약 정의해야할 매크로가 더 많다면 귀찮은 일이다. 또, CLUBs
, DIAMONDS
, HEARTS
, SPADES
처럼 정의된 이름은 전처리기에 의해 제거되므로 디버깅과정에서 이 이름을 이용할 수 없다.
C언어는 여러 값의 집합을 가지는 변수에 대해 명시적으로 설계할 수 있는 특별한 종류의 자료형을 제공한다. enumerated type
은 프로그래머에 의해 "열거된(enumerated)" 값을 가지는 자료형이다. 각각의 값들은 반드시 이름(enumeration constant)을 가져야한다. 아래의 예시는 변수 s1
과 s2
에 대입될 수 있는 값(CLUBS
, DIAMONDS
, HEARTS
, SPADES
)을 열거한(enumerate) 것이다.
enum {CLUBS, DIAMONDS, HEARTS, SPADES} s1, s2;
열거형은 구조체와 공용체와 공통되는 부분이 거의 없지만, 비슷한 방식으로 선언된다. 그러나 구조체나 공용체의 멤버와는 다르게, 열거형 상수의 이름은 scope 안에서 선언된 다른 식별자(identifiers)와는 반드시 달라야한다.
열거형 상수는 #define
directive를 통해 생성된 상수와 비슷하지만, 동일하지는 않다. 열거형 상수는 C의 scope 규칙을 따른다. 열거형이 함수 내부에서 선언되었다면, 열거형 상수는 함수 바깥에서 인식되지 않는다.
Enumeration Tags and Type Names
열거형에 대한 이름을 만들 필요가 있을 수도 있는데, 구조체와 공용체에서와 같은 이유이다. 구조체와 공용체처럼 열거형에도 이름을 붙이는 두 가지 방법이 있다. tag를 선언하거나 typedef
를 사용하여 자료형의 이름을 만드는 것이다.
열거형 tag는 구조체와 공용체 tag와 비슷하다. tag suit
를 정의하기 위해, 아래처럼 작성할 수 있다.
enum suit {CLUBS, DIAMONDS, HEARTS, SPADES};
suit
변수는 아래와 같은 방식으로 선언된다.
enum suit s1, s2;
이것말고도, typedef
를 사용해 자료형 이름으로 Suit
를 만들 수 있다.
typedef enum {CLUBS, DIAMONDS, HEARTS, SPADES} Suit;
Suit s1, s2;
C89에서는, typedef
를 사용하여 열거형에 이름을 붙이는 것은 Boolean 자료형을 만드는 훌륭한 방법이다.
typedef enum {FALSE, TRUE} Bool;
C99는 내장된 Boolean 자료형을 가지고 있어서, Bool
자료형을 이 방식으로 정의할 필요가 없다.
Enumerations as Integers
C언어는 열거형 변수와 상수를 정수로써 처리한다. 기본적으로, 컴파일러는 특정한 열거형 내부에서 정수 0, 1, 2,...을 대입한다. 예를 들어 suit
열거형에서 CLUBS
, DIAMONDS
, HEARTS
, SPADES
는 각각 0, 1, 2, 3을 나타낸다.
만약 우리가 원한다면 열거형 상수에 다른 값을 선택하는 것은 자유이다. CLUBS
, DIAMONDS
, HEARTS
, SPADES
가 각각 1, 2, 3, 4를 가지게 하고싶다고 생각해보자. 그러면 우리는 열거형을 선언할 때 아래의 예시처럼 이 숫자를 명시할 수 있다.
enum suit {CLUBS = 1, DIAMONDS = 2, HEARTS = 3, SPADES = 4};
열거형 상수의 값은 자의적인(arbitrary) 정수가 될 수 있고, 특별한 순서 없이 열거될 수 있다.
enum dept {RESEARCH = 20, PRODUCTION = 10, SALES = 25};
심지어 같은 값을 가지는 2개 이상의 열거형 상수또한 규칙에 어긋나지 않는다. 열거형 상수에 어떠한 값도 명시되지 않았을 때, 열거형 상수의 값은 이전의 상수의 값보다 1씩 커진다(기본적으로 첫번째 열거형 상수의 값은 0이다). 아래의 열거형에서, BLACK
은 0의 값을 가지고, LT_GRAY
는 7, DK_GRAY
는 8, WHITE
는 15의 값을 가진다.
enum EGA_colors {BLACK, LT_GRAY = 7, DK_GRAY, WHITE = 15};
열거형 값은 얇게 위장한(thinly disguised) 정수에 불과할 뿐이기 때문에, C언어는 일반적인 정수와 함께 이 열거형 값을 혼합하는 것을 허용한다.
int i;
enum {CLUBS, DIAMONDS, HEARTS, SPADES} s;
i = DIAMONDS; /* i is now 1 */
s = 0; /* s is now 0 (CLUBS) */
s++; /* s is now 1 (DIAMONDS) */
i = s + 2; /* i is now 3 */
컴파일러는 어떠한 정수 자료형의 변수인 것처럼 s
를 처리할 것이다. CLUBS
, DIAMONDS
, HEARTS
, SPADES
는 정수 0, 1, 2, 3에 대한 이름일 뿐이다.
열거형 값을 정수처럼 사용하는 것이 가능하고 편리하더라도, 정수를 열거형 값처럼 사용하는 것은 위험하다. 예를 들어, s
에 대응되는 것이 없는 4와 같은 숫자를 저장할 수도 있다.
Using Enumerations to Declare "Tag Fields"
열거형은 Section 16.4에서 마주한 마지막으로 값이 대입된 공용체의 멤버를 결정하는 문제를 해결하는 것에 완벽하다. Number
구조체에서, kind
멤버를 int
대신에 열거형으로 만들 수 있다.
typedef struct {
enum {INT_KIND, DOUBLE_KIND} kind;
union {
int i;
double d;
} u;
} Number;
위에서 새로 수정한 구조체는 Section 16.4에서 마주한 구조체와 실질적으로 동일하게 사용된다. 이것의 장점은 INT_KIND
와 DOUBLE_KIND
매크로를 없앴고(이제는 열거형 상수), kind
의 의미를 더 명확하게 만들었다는 점이다. kind
는 오직 INT_KIND
, DOUBLE_KIND
2개의 숫자만 가질 수 있다는 것이 명백해졌다.
Others
구조체 내부의 바이트 수를 결정하기 위해 sizeof
연산자를 사용했을 때, 각 멤버들의 크기를 모두 더한 것보다 더 큰 값이 나왔다. 어떻게 이렇게 된 것인가?
예시를 한번 보자.
struct {
char a;
int b;
} a;
char
값이 1byte이고, int
값이 4byte라면, s
의 크기는 얼마일까? 답은 명백하게 5byte이지만 이는 정답이 아니다. 어떤 컴퓨터는 특정 항목의 주소가 특정 byte의 배수(multiple)여야 한다(전형적으로 2, 4, 8이고 이는 항목의 자료형에 의존한다). 이 조건을 만족시키기 위해서, 컴파일러는 인접한 멤버들 사이에 사용되지 않는 바이트인 “구멍(hole)”을 남기는 것으로 구조체를 “정렬”한다. 만약 데이터 항목이 반드시 4byte의 배수로 시작한다고 가정한다면, 구조체 s
의 멤버 a
는 3byte의 구멍이 생긴다. 결과적으로 sizeof(s)
는 8이다.
멤버들 사이에 구멍을 남길 뿐만 아니라, 구조체의 끝부분에도 구멍을 가질 수도 있다. 예를 들어 아래의 구조체는 b
멤버에 3바이트의 구멍을 가질 수 있다.
struct {
int a;
char b;
} s;
구조체의 시작 부분에도 “구멍”이 있을 수 있는가?
없다. C표준에서는 구멍은 오직 멤버들 사이에 있거나, 마지막 멤버의 뒤에 있는 것만 허용한다고 명시하고 있다. 이에 대한 결과로, 구조체의 첫번째 멤버를 가리키는 포인터는 전체 구조체를 가리키는 포인터와 같은 것이 보장이 된다.(그러나, 두 개의 포인터는 같은 자료형을 가지지 않는다).
두 개의 구조체가 동일한지 검사하기 위해 ==
연산자를 쓰는 것은 규칙에 맞는가?
이 연산은 C언어의 철학과 일관되지 않기 때문에 C언어에서 제외되었다. 구조체 멤버를 하나씩 비교하는 것은 너무 비효율적이다. 구조체 내부의 모든 byte를 비교하는 게 더 나을 것이다(많은 컴퓨터는 이러한 비교를 순식간에 수행하는 특별한 instruction을가지고 있다). 하지만 만약 구조체가 구멍을 가지고 있다면, byte 비교가 잘못된 답을 낼 것이다. 심지어는 대응되는 멤버가 동일한 값을 가지더라도, 구멍에 남아있는 데이터가 다를 수도 있다. 이 문제는 컴파일러에게 구멍에 저장된 값이 항상 같은 값(예를 들면 0)이라는 것을 확실히 해주는 것으로 해결할 수 있다. 구멍을 초기화하는 것은 구조체를 사용하는 모든 프로그램에 성능에 있어서 페널티를 부과하는데, 그렇기 때문에 이는 실현가능하지 않다.
왜 C언어는 구조체 자료형의 이름을 짓는 것(tag와 typedef
)에 두 가지 방법을 제공하는가?
C언어는 원래 typedef
가 없었고 그래서 구조체 자료형에 이름을 짓는 기법은 tag밖에 없었다. typedef
가 추가되었을 때, 이미 tag를 제거하기에 너무 늦었다. 게다가 tag는 구조체의 멤버가 동일한 자료형의 구조체를 가리킬 때 여전히 필요하다(Section 17.5에서 node
구조체에 대해 알아볼 것이다).
구조체는 tag와 typedef
이름을 둘 다 가질 수 있는가?
사실 tag와 typedef
이름은 동일할 수 있다.
typedef struct part {
int number;
char name[NAME_LEN+1];
int on_hand;
} part;
프로그램 내부의 여러 파일들 간에 어떻게 구조체 자료형을 공유할 수 있는가?
구조체 tag(또는 원한다면 typedef
)의 선언을 헤더파일에 넣고, 그 후 구조체가 필요한 곳에 헤더파일을 포함시키면 된다. part
구조체를 공유하기 위해, 아래의 행을 헤더파일에 포함시키면 된다.
struct part {
int number;
char name[NAME_LEN+1];
int on_hand;
};
이 자료형의 변수를 선언한 것이 아니고, 오직 구조체 tag만 선언했다는 점을 주목하자.
구조체 tag 또는 구조체 자료형의 선언을 가지는 헤더파일은 다중 포하으로부터 보호할 필요가 있다. tag나 typedef
이름을 같은 파일에 두번 선언하는 것은 에러이다. 이는 공용체나 열거형에도 동일하게 적용된다.
만약 part
구조체의 선언을 다른 두 개의 파일에 포함시킨다면, 한 파일의 내부에 있는 part
변수와 또다른 파일의 내부에 있는 part
변수는 동일한 자료형인가?
기술적으로는 동일하지 않다. 그러나 C표준에서는 한 파일의 내부에 있는 part
자료형은 또다른 파일의 내부에 있는 part
자료형과 호환 가능하다고 말한다. 호환 가능한 자료형의 변수들은 서로 대입가능한데, 그렇기에 자료형 사이에 “호환가능한”과 “같다”라는 말은 실질적으로 작은 차이밖에 없다.
C89과 C99는 구조체 호환성에 대한 규칙이 조금 다르다. C89에서는, 멤버들이 같은 이름을 가지고, 같은 순서로 나타나고, 멤버들이 호환가능한 자료형을 가진다면, 서로 다른 파일에 정의된 구조체가 호환 가능하다. C99에서는 딱 한 단계 더 있는데, 두 개의 구조체 모두다 같은 태그를 가지던지, 아니면 둘다 태그를 가지지 않아야 한다는 점이다.
비슷한 호환성 규칙이 공용체와 열거형에도 적용된다(C89와 C99 사이의 차이점은 동일하다).
복합 리터럴(compound literal)에 대한 포인터를 가지는 것은 규칙에 어긋나는가?
규칙에 맞다. Section 16.2의 함수인 print_part
함수를 생각해보자. 현재, 이 함수에 대한 parameter는 part
구조체이다. 만약 part
구조체 대신 여기에 대한 포인터로써 전달하도록 수정한다면 함수는 더 효율적으로 될 것이다. 복합 리터럴을 출력하는 함수를 사용하는 것은 &
(주소) 연산자를 앞에 붙인 argument에 의해 이루어질 수 있다.
print_part(&(struct part) {528, "Disk drive", 10});
복합 리터럴에 대한 포인터를 허용하는 것은 리터럴을 수정 가능한 것처럼 보이게 만든다. 이게 가능한가?(C99)
그렇다. 복합 리터럴은 수정 가능한 lvalue이다. 물론 수정하는 것은 드물다.
열거형 내부의 마지막 상수 끝에 콤마(,
)를 붙이는 프로그램을 본적이 있다. 이 행동은 규칙에 맞는가?
enum gray_values {
BLACK = 0,
DARK_GRAY = 64,
GRAY = 128,
LIGHT_GRAY = 192,
};
이 행동은 C99에서는 규칙에 맞다(몇 개의 C99 이전의 컴파일러 또한 지원할 수도 있다). 마지막에 "후행 콤마(trailing comma)"은 열거형을 더 쉽게 수정할 수 있도록 하는데, 왜냐하면 우리는 코드의 행을 수정할 필요 없이 열거형의 끝부분에 상수를 추가할 수 있기 때문이다. 예를 들어, 우리가 열거형에 WHITE
를 추가하고 싶다면 아래처럼 그냥 추가하면 된다.
enum gray_value {
BLACK = 0,
DART_GRAY = 64,
GRAY = 128,
LIGHT_GRAY = 192,
WHITE = 255,
};
LIGHT_GRAY
의 정의 이후에 있는 콤마는 WHITE
를 목록 끝부분에 추가하기 쉽도록 만든다.
이 변화의 한 가지 이유는, C89에서는 initializer 내부의 후행 콤마를 허용했었고, 이는 열거형 내부에서 이와 동일한 유연성을 허용하지 않은 것이 일관되지 않는다고 보였기 때문이다. 또 C99는 복합 리터럴 내부에 후행 콤마를 허용한다.
열거형의 값은 subscript로써 사용될 수 있는가?
당연하게 가능하다. 열거형의 값은 정수이고, 기본적으로 0부터 시작하여 점점 커지면서 세어지기(count) 때문에 subscript로 사용될 수 있다. 또, C99에서는 열거형 상수가 designated initializer 내부에서 subscript로써 사용될 수 있다. 아래에 예시가 있다.
enum weekdays {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY};
const char *daily_specials[] = {
[MONDAY] = "Beef ravioli",
[TUESDAY] = "BLTs",
[WEDNESDAY] = "Pizza",
[THURSDAY] = "Chicken fajitas",
[FRIDAY] = "Macaroni and chesse"
};
Author And Source
이 문제에 관하여(16. Structures, Unions, and Enumerations(2)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@hamoci/16.-Structures-Unions-and-Enumerations2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)