structure는 배열과는 다른 두가지 특징을 가진다.
1. 각 elements(member라고 함)가 같은 type일 필요가 없다.
2. 각 elements는 (위치가 아닌) 이름을 가지고 있다.
struct {
int number;
char name[NAME_LEN+1];
int on_hand;
} part1;
다른 type의 변수를 선언하는 것과 다르지 않다.
struct {...}
가 type을 나타내고, part1
이 변수명을 나타낸다.
structure의 members는 선언된 순서대로 memory에 저장된다.
structure내에서 선언된 member의 이름은 그 안에서 scope를 가지므로 scope 밖의 다른 name과 충돌하지 않는다.(C 용어로 "각 structure는 members에 대해 분리된 name space를 가진다"고 한다.)
array처럼 structure도 declare할 때 initialize할 수 있다.
struct {
int number;
char name[NAME_LEN+1];
int on_hand;
} part1 = {12, "HDD", 5}; //값의 순서는 member 순서와 동일해야 한다.
array처럼 initializer에 constant가 와야하고,
(C99부턴 꼭 그럴 필요 없음, automatic storage duration이면 initializer가 constant 일 필요 X (section18.5))
array처럼 braces로 감싸서 member 순서에 맞게 initialize한다.
array처럼 initializer를 비워도 되는데, 비우면 이는 0으로 초기화된다.(array는 완전히 비우는건 illegal이니까 얘도 완전히 비우진 말자.)
initializer에 다른 형태의 proper type이 와도 된다. (18.5)
예를들어 initializer 자체가 해당 structure type의 함수 return값이거나 변수여도 OK (C99)
근데 constant가 아닌 걸로 initialize할 때는
해당 structure가 static으로 선언돼 있으면 안된다.(p.385)
static으로 선언해버리면 애초에 constant initializer 밖에 못옴.
(p.386, Section 18.5 참고)
array에서 썼던 designated initializers도 사용할 수 있다.
{.number = 12, .name = "HDD", .on_hand = 5}
member 이름과 앞에 점의 결합(.abc
)을 designator라고 한다.(array의 designator는 [1]
이런식으로 생김)
designator 없어도 그 전 designator로 위치를 추측하게 할 수 있다. initializer에서 찾지 못한 member는 0으로 초기화된다.
장점 : 알아보기 편함, 순서 바껴도 OK, 나중에 member들 순서 바껴도 OK
Slecting member
structure-name.member-name
여기 나오는 period(.
)는 C operator다. precedence는 postfix ++/--와 같다.
member는 lvalue다.
Assignment
part1 = part2;
part2의 모든 member 하나하나 part1에 copy한다.
compatible type 일때만 assignment를 할 수 있다.
array는 자기들끼리 assignment가 안되는데, structure안에 array는 copy가 된다.
이 두가지 왜 ==
라던가 !=
같은 다른 operator는 제공하지 않는다.
compatible type?? (아래 Q&A에 rule 더 있음)
동시에 선언된 두 structure는 compatible
같은 structure tag나 같은 type name을 사용해서 선언된 structure도 compatible
(tag나 typedef로 만든 type name 사용하면 동시에 선언 안해도 compatible)
구조체 변수가 한번에 다 선언되면 좋겠지만, 다른 point에서 선언해야 할 경우가 많다.
그렇다고 매번 구조체 변수 선언할때마다 구조체 type을 길게 기술하자니 코드도 길어지고, 무엇보다 그렇게 선언하면 같은 모양새여도 compatible type이 되지 않는다.
그리고 함수에 넘겨줄때도 type 이름을 몰라서 넘겨주지 못한다.
따라서 structure type을 naming하는 게 필요하다.
struct part { //part라는 이름의 structure tag
int number;
char name[NAME_LEN+1];
int on_hand;
}; //semicolon 붙이기
이렇게 structure tag를 만들어 놓고 나서
struct part part1, part2;
part1과 part2는 compatible
이런 식으로 declare할 수 있다.(struct가 붙어야됨. part만 있어선 type name이 아니기 때문에 아무 의미가 없다.)
sturct가 붙어야 기능하므로, part 이름 자체는 다른 name과 충돌하지 않는다.
즉, 저렇게 structure tag를 만들어도 part라는 이름의 변수를 선언하는게 가능하다.
structure tag를 만들면서 동시에 변수를 선언해도 된다.
struct part {
int number;
char name[NAME_LEN+1];
int on_hand;
} part1;
typedef struct {
int number;
char name[NAME_LEN+1];
int on_hand;
} Part;
이것도 그냥 다른 typedef랑 똑같다.
struct {...}
전체가 하나의 type이니 Part는 당연히 제일 뒤에 와야된다.
Part part1, part2;
part1과 part2는 compatible
tag랑 typedef 중에 골라서 쓰면 된다.
하지만 linked list를 만들때는 무조건 tag를 써야된다.
(다음 node를 가리킬 pointer member를 만들기 위해)
structures를 함수 parameter로 받거나 return할 수 있다.
주의 : 함수에 structure를 넘겨주거나 넘겨받을 때 copy가 일어나기 때문에 structure 크기가 크다면 시간을 많이 잡아먹는다. 따라서 구조체의 pointer로 넘겨주는 것도 좋은 방법이다. (storage duration을 잘 확인해야됨. automatic이면 함수 끝날때 사라지므로 pointer 넘겨줘도 써먹질 못함)
sturcture pointer를 사용하는 또 다른 이유는, 해당 stucture가 program 내에서 unique 해야될 경우 그렇다.(copy를 만들지 않음)
예를들어 <stdio.h>에는 FILE
이라는 structure가 있는데, 열려있는 file에 대한 정보를 저장하는 structure이므로 unique해야된다. 그래서 FILE
구조체를 "주고 받는" 함수들은 모두 해당 구조체의 pointer를 사용한다.
(struct part) {12, "HDD", 5};
얘도 array compound literal처럼 함수에 넘겨주거나 함수에서 반환하거나 변수에 저장하는 등의 목적으로 사용된다.
앞 괄호에는 struct part
처럼 tag가 올 수도 있고, Part
처럼 typedef를 통해 만든 name이 올 수도 있다.
part1 = (struct part) {12, "HDD", 5};
이런식으로 compund literal을 변수에 저장할때, initializer처럼 보여도 둘은 같지 않다.
designator을 사용해서 만들어도 된다.
structure 안에 structure가 있다면, 두개의 .
operator를 통해 접근할 수 있다.
structure안에 structure를 둠으로써 그 데이터를 개별 데이터로 관리할 수 있다.
(예를들어 학생 구조체 안에 이름 구조체를 멤버로 둬서 학생 이름을 관리한다면, 학생 이름이 필요한 함수에 넘길때도 편하고 이름만 copying하기도 편하다.)
structure가 element인 array는 간단한 database가 된다.
위에서 봤겠지만 structure를 initializing할 때 {}
를 써서 array와 같은 모양새로 초기화한다.
그렇기때문에 array of structure를 초기화하면 마치 2차원배열을 초기화하는 것과 같은 모양이 된다.
struct dialing_code {
char *country;
int code;
};
const struct dialing_code country_code[] =
{{"Argentina", 54}, {"Bangladesh", 880},
{"Brazil", 55}, {"Egypt", 20}};
inner braces are optional
두개 이상의 designator도 허용한다.
struct part inventory[100] =
{ [0].number = 528, [0].on_hand = 10, [0].name[0] = '\0'};
첫번째 두번째는 designator 2개, 세번째는 designator 3개
array of structure를 database로 이용하는 프로그램인데 자세한건 p.389
메모할만한 부분이 있다면,
1. scanf로 character 하나 입력 받을 때 " %c"
이렇게 앞에 띄어쓰기를 넣어서 이전에 입력하기 위해 쳤던 엔터키는 skip되도록 처리함
2. read_line 함수는 비록 함수 하나지만, main 파일의 다른 함수들과 연관성도 없고 다른 프로그램에서 재사용할 가능성도 있어서 아예 독립된 파일로 만들어서 작성함
union {
int i;
double d;
} u;
structure와 마찬가지로 여러 type의 member가 올 수 있지만, 차이점은 union은 가장 큰 member만큼의 저장 공간만 할당된다는 점이다.(구조체는 각 member가 다른 주소를 가지지만, union은 같은 주소를 가진다.)
union은 structure와 거의 동일한 특징을 같는다.
1. u.i = 7;
member 접근
2. =
operator
3. union tag
4. union type
5. passed to function, returned by function
6. 초기화 할 때 constant여야 함.(얘도 structure와 마찬가지로 C99부터는 꼭 constant일 필요는 없고 compatible type이면 OK)
7. designated initializer
initializing을 할 때 차이가 하나 있다. 똑같이 {}
는 사용하지만, 제일 처음 멤버만 초기화 할 수 있다. (designated initializer 사용하면 꼭 처음 member일 필요는 없음)
structure에서 주로 공간을 절약하기위해 union을 사용한다.
아래 예시 처럼 공통되는 부분은 남겨두고 다른 부분은 union으로 묶으면 공간을 절약할 수 있다. (저걸 다 풀어서 쓰면 공간 엄청 차지 함)
ex)
struct satalog_item {
int stock_numer;
double price;
int item_type;
union {
struct {
char title[TITLE_LEN+1];
char author[AITHOR_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;
};
union의 특징 하나
보통 union에 저장된 value를 다른 member를 통해 접근하는 것은 좋지않다.(하나가 저장되면 다른 것들은 undefined이므로)
그러나 union의 멤버 두개 이상이 structures이고, 그 structures의 member가 compatible type으로 시작한다면,
구조체 둘 중 하나가 valid라면 matching memers in the other structure 또한 valid하다.
예를들어 위 union에서 u.item.mug 가 valid라면, u.item.mug.design과 u.item.shirt.design 또한 같은 값으로 사용할 수 있다.
여러 type이 섞인 data를 생성할 수 있다.
예를들어 array는 보통 하나의 type으로만 이루어져 있지만, union을 사용하면 여러 type을 섞어서 사용할 수 있다.
typedef union {
int i;
double d;
} Number;
Number number_array[100];
number_array[0].i = 1;
Discussed in Section 20.3
union의 큰 문제는 현재 저장된 값이 어떤건지 어떤 type인지 알아보기 힘들다는 것이다.
#define INT_KIND 0
#define DOUBLE_KIND 1
typedef struct {
int kind; //tag field (discriminant)
union {
int i;
double d;
} u;
} Number;
그래서 이렇게 union을 structure 안에 넣어서 어떤 type인지 표기하는 tag field를 만든다.
이제 변수 값 바뀔 때마다 tag field의 값도 수정해주면 된다.
프로그램 만들다 보면 작은 규모의 set만을 값으로 가지는 변수가 필요하기도하다. (ex. boolean, card game)
그래서 C에선 enumerated type을 제공한다.
enumerated type이란 해당 type의 가능한 값과 이름(enumeratrion constant)이 프로그래머에 의해 정해진 type이다.
enum {CLUBS, DIAMONDS, HEARTS, SPADES} s1;
structure나 union과 다른 점은, 그 둘은 별도의 name space가 있어서 그 안의 identifier가 다른 애들과 충돌하지 않았지만 enumeration은 해당 scope 내에서 identifier를 구분해야 한다.
enumeration constant가 macro definition과 비슷해 보일 수 있는데, 전자는 C's scope rule을 따른다는 점에서 다르다.
Enumeration도 structure나 union처럼 enumeration tag나 type name을 만들 수 있다.
enum suit {CLUBS, DIAMONDS, HEARTS, SPADES};
typedef enum {CLUBS, DIAMONDS, HEARTS, SPAEDS} Suit;
C는 기본적으로 enumeration variable과 enumeration constant를 integer로 취급한다.
그래서 다른 ordinary integer과 섞어서 사용해도 OK (쓸거면 조심해서 써야됨)
enumertaion 내에 선언된 constant 순서에 따라 0, 1, 2, ...가 각각 assign된다.
enum suit {CLUBS = 1, DIAMONDS = 5, HEARTS = 3, SPADES = 5};
이런 식으로 값을 정해줄 수도 있다.
같은 값 가지는 것도 legal
값을 빼먹어도 되는데, 이러면 전 값에서 더하기 1을 한 값으로 정해진다. 첫번째 constant 값이 안적혀있으면 이는 0으로 정해진다.
typedef struct {
enum {INT_KIND, DOUBLE_KIND} kind;
union {
int i;
double d;
} u;
} Number;
sizeof operator를 사용해서 structure의 size를 구했는데 더 큰 값이 나왔다 왜지?
몇몇 컴퓨터는 특정 data의 주소가 특정 byte 숫자의 배수가 되도록 하고 있다. 이런 조건을 만족하기 위해 compiler는 holes(unused bytes)을 남겨두고 저장을 한다.
그래서
struct {
char a;
int b;
} s;
4byte의 배수로 시작해야된다면, 이 구조체의 크기는 8byte가 되는 것이다.
holes은 (1)member들 중간에 오거나 (2)구조체 제일 끝에 오거나, 둘 중 한곳에 온다고 C standard에 기술돼있다.
왜 structure에서 ==
operator를 못쓰나?
C philosophy에 맞게 해당 작업을 실행할 방법이 없다.
member 하나씩 비교하는 것은 비효율적이고,
한 byte씩 비교하자니 holes 때문에 제대로 된 결과가 나오지 않을 수 있다.(member값이 다 같아도 hole값은 다를 수 있어서)
그렇다고 holes을 모두 초기화 하자니 structure를 사용하는 모든 program에 performance penalty가 돼서 딱히 실현가능하지 않다.
왜 structure type을 정의하는 방법이 두개나 있지?
tag가 먼저였는데, 나중에 typedef가 추가됐다. 그때 와서 tag를 제거하기엔 늦었기도했고, tag는 본인 구조체를 가리키는 포인터 멤버를 선언하기 위해서 계속 필요했다.
tag와 typedef 동시에 만들어도 되나?
YES, 심지어 이름도 같아도 됨
typedef struct part{
int number;
char name[NAME_LEN+1];
int on_hand;
} part;
structure type을 다른 파일들과 공유하려면?
Header file에
1. Declare only structure tag(or a typedef), not variables of this type
2. Protection against multiple inclusion is needed (애초에 그걸 보호하는 이유가 typedef 때문임)
union과 enumeration도 이렇게 공유하면 된다.
part structure의 declaration을 두개의 다른 file에 넣으면 각각에서 선언한 variables은 same type인가?
No.
같지는 않지만, compatible type이다.
compatible type과 same type에는 차이가 있다.
structure의 compatibility에 대한 rule은 아래와 같다.
1.(C89) 다른 파일의 structure가 same name을 가지고 same order로 상응하는 member가 온다면 이는 compatible이다.
2.(C99) 1번에 추가로 같은 tag를 가지거나 둘 다 tag가 없어야 한다.
비슷한 룰이 union과 enumertion에도 적용된다.
compund literal에 대해 pointer를 만들어도 legal?
Yes. (storage duration 유의하긴 해야될듯)
ex)
print_part(&(struct part) {10, "HDD", 2});
아래 code처럼 comma가 마지막에 붙어도 괜찮나?
enum gray_values {
BLACK = 0,
DARK_GRAY = 64,
GRAY = 128,
LIGHT_GRAY = 192, //trailing comma is legal
};
C99부터 허용됐다. 이러면 원래 있던 code를 수정하지 않고도 추가할 수 있다.
또 C89에서 initializer에 trailing comma를 허용했었는데 C99부터 일관성을 위해 enumeration에서도 이를 허용한 것이다.
enumerated type을 subscripts에 쓴 예시
enum weekdays {MONDAY, TUESDAY, WEDNESDAY};
const char *daily_specials[] = {
[MONDAY] = "Pizza",
[TUESDAY] = "Hot dog",
[WEDNESDAY] = "Chicken"
};