구조체 선언(Struct declaration)
구조체 선언(Struct declaration)
서로 다른 타입의 데이터를 한 덩어리로 묶어 순서대로 배치하고 싶을 때 쓰는 게 구조체(struct)예요. 멤버들의 저장 공간이 겹치는 union과 달리, 구조체는 멤버들의 저장 공간이 정의된 순서대로 차례로 할당되는 타입이에요.
구조체의 타입 지정자(type specifier)는 쓰는 키워드만 빼고 union 타입 지정자와 동일해요.
출처: cppreference
본문
문법
struct attr-spec-seq(선택) name(선택) { struct-declaration-list } |
(1) |
struct attr-spec-seq(선택) name |
(2) |
- 구조체 정의: 새로운 타입
struct name을 도입하고 그 의미를 정의해요. struct name ;처럼 한 줄에 단독으로 쓰면struct name을 정의하지 않고 선언만 해요(아래 전방 선언 참고). 다른 맥락에서는 이전에 선언된 구조체를 가리키고, 이때 attr-spec-seq는 허용되지 않아요.
| 항목 | 의미 | |
|---|---|---|
| name | - | 정의되는 구조체의 이름 |
| struct-declaration-list | - | 임의 개수의 변수 선언, 비트 필드 선언, 정적 단언 선언. 불완전 타입의 멤버와 함수 타입의 멤버는 허용되지 않아요(아래의 유연 배열 멤버는 제외) |
| attr-spec-seq | - | (C23부터) 구조체 타입에 적용되는 선택적 속성 목록 |
설명(Explanation)
구조체 객체 안에서, 그 요소들(및 비트 필드 할당 단위)의 주소는 멤버가 정의된 순서대로 증가해요. 구조체 포인터는 그것의 첫 번째 멤버에 대한 포인터로 캐스팅될 수 있어요(멤버가 비트 필드면 그 할당 단위로). 마찬가지로 구조체의 첫 번째 멤버에 대한 포인터는 감싸는 구조체 포인터로 캐스팅될 수 있어요. 구조체의 두 멤버 사이 또는 마지막 멤버 뒤에는 이름 없는 패딩이 있을 수 있지만, 첫 번째 멤버 앞에는 없어요. 구조체의 크기는 멤버 크기의 합보다 적어도 큽니다(패딩 때문에).
구조체가 이름 있는 멤버를 하나 이상 정의하면, 마지막 멤버를 불완전 배열 타입으로 추가 선언하는 것이 허용돼요. 유연 배열 멤버(flexible array member)의 요소에 접근하면(유연 배열 멤버의 이름을 오른쪽 피연산자로 . 또는 -> 연산자를 쓰는 표현식에서), 구조체는 마치 배열 멤버가 이 객체에 할당된 메모리에 맞는 가장 긴 크기를 가진 것처럼 동작해요. 추가 저장 공간이 할당되지 않았다면 요소 1개의 배열처럼 동작하는데, 단 그 요소가 접근되거나 그 요소의 한 칸 뒤 포인터가 만들어지면 동작은 정의되지 않아요. 초기화와 대입 연산자는 유연 배열 멤버를 무시해요. sizeof는 그것을 빼고 계산하지만, 뺀 것보다 더 많은 꼬리 패딩을 가질 수 있어요. 유연 배열 멤버를 가진 구조체(또는 유연 배열 멤버를 가진 구조체를 재귀적으로 포함할 수 있는 멤버를 가진 union)는 배열 요소나 다른 구조체의 멤버로 나타날 수 없어요.
struct s { int n; double d[]; }; // s.d is a flexible array member
struct s t1 = { 0 }; // OK, d is as if double d[1], but UB to access
struct s t2 = { 1, { 4.2 } }; // error: initialization ignores flexible array
// if sizeof (double) == 8
struct s *s1 = malloc(sizeof (struct s) + 64); // as if d was double d[8]
struct s *s2 = malloc(sizeof (struct s) + 40); // as if d was double d[5]
s1 = malloc(sizeof (struct s) + 10); // now as if d was double d[1]. Two bytes excess.
double *dp = &(s1->d[0]); // OK
*dp = 42; // OK
s1->d[1]++; // Undefined behavior. 2 excess bytes can't be accessed
// as double.
s2 = malloc(sizeof (struct s) + 6); // same, but UB to access because 2 bytes are
// missing to complete 1 double
dp = &(s2->d[0]); // OK, can take address just fine
*dp = 42; // undefined behavior
*s1 = *s2; // only copies s.n, not any element of s.d
// except those caught in sizeof (struct s)
union과 유사하게, 이름 없는 struct 타입의 이름 없는 멤버를 익명 구조체(anonymous struct)라고 불러요. 익명 구조체의 모든 멤버는 감싸는 구조체나 union의 멤버로 간주되고, 구조 배치는 유지돼요. 이는 감싸는 구조체나 union도 익명이면 재귀적으로 적용돼요.
struct v
{
union // anonymous union
{
struct { int i, j; }; // anonymous structure
struct { long k, l; } w;
};
int m;
} v1;
v1.i = 2; // valid
v1.k = 3; // invalid: inner structure is not anonymous
v1.w.k = 5; // valid
union과 유사하게, 이름 없는 멤버(익명 중첩 구조체나 union으로 얻은 것 포함) 없이 구조체를 정의하면 프로그램의 동작은 정의되지 않아요. (C11부터)
전방 선언(Forward declaration)
다음 형태의 선언은
struct attr-spec-seq(선택) name ; |
태그 이름 공간에서 name에 대한 이전에 선언된 의미를 숨기고, name을 현재 범위에서 나중에 정의될 새로운 struct 이름으로 선언해요. 정의가 나타날 때까지 이 struct 이름은 불완전 타입이에요.
이를 통해 서로를 참조하는 구조체를 만들 수 있어요:
struct y;
struct x { struct y *p; /* ... */ };
struct y { struct x *q; /* ... */ };
새 struct 이름은 다른 선언 안에서 struct 태그를 사용하는 것만으로도 도입될 수 있다는 점에 유의하세요. 단, 태그 이름 공간에 같은 이름의 이전에 선언된 struct가 있으면 그 태그는 그 이름을 가리키게 돼요.
struct s* p = NULL; // tag naming an unknown struct declares it
struct s { int a; }; // definition for the struct pointed to by p
void g(void)
{
struct s; // forward declaration of a new, local struct s
// this hides global struct s until the end of this block
struct s *p; // pointer to local struct s
// without the forward declaration above,
// this would point at the file-scope s
struct s { char* p; }; // definitions of the local struct s
}
키워드(Keywords)
struct
주의할 점(Notes)
구조체 이니셜라이저의 규칙은 구조체 초기화 문서를 참고하세요.
불완전 타입의 멤버가 허용되지 않고, 구조체 타입은 정의가 끝날 때까지 완전해지지 않으므로, 구조체는 자기 자신의 타입을 멤버로 가질 수 없어요. 자기 자신에 대한 포인터는 허용되는데, 연결 리스트나 트리의 노드를 구현할 때 흔히 쓰여요.
구조체 선언은 범위를 만들지 않으므로, struct-declaration-list 안의 선언으로 도입된 중첩 타입, 열거형, 열거자는 구조체가 정의되는 주변 범위에서 보여요.
예제(Example)
#include <stddef.h>
#include <stdio.h>
int main(void)
{
// Declare the struct type.
struct car
{
char* make;
int year;
};
// Declare and initialize an object of a previously-declared struct type.
struct car c = {.year = 1923, .make = "Nash"};
printf("1) Car: %d %s\n", c.year, c.make);
// Declare a struct type, an object of that type, and a pointer to it.
struct spaceship
{
char* model;
int max_speed;
} ship = {"T-65 X-wing starfighter", 1050},
*pship = &ship;
printf("2) Spaceship: %s. Max speed: %d km/h\n\n", ship.model, ship.max_speed);
// Address increase in order of definition. Padding may be inserted.
struct A { char a; double b; char c; };
printf(
"3) Offset of char a = %zu\n"
"4) Offset of double b = %zu\n"
"5) Offset of char c = %zu\n"
"6) Size of struct A = %zu\n\n",
offsetof(struct A, a),
offsetof(struct A, b),
offsetof(struct A, c),
sizeof(struct A)
);
struct B { char a; char b; double c; };
printf(
"7) Offset of char a = %zu\n"
"8) Offset of char b = %zu\n"
"9) Offset of double c = %zu\n"
"A) Size of struct B = %zu\n\n",
offsetof(struct B, a),
offsetof(struct B, b),
offsetof(struct B, c),
sizeof(struct B)
);
// A pointer to a struct can be cast to a pointer
// to its first member and vice versa.
char** pmodel = (char **)pship;
printf("B) %s\n", *pmodel);
pship = (struct spaceship *)pmodel;
}
가능한 출력:
1) Car: 1923 Nash
2) Spaceship: T-65 X-wing starfighter. Max speed: 1050 km/h
3) Offset of char a = 0
4) Offset of double b = 8
5) Offset of char c = 16
6) Size of struct A = 24
7) Offset of char a = 0
8) Offset of char b = 1
9) Offset of double c = 8
A) Size of struct B = 16
B) T-65 X-wing starfighter
멤버 선언 순서가 크기에 어떻게 영향을 주는지 볼 수 있어요. struct A는 char, double, char 순서라서 double(8바이트) 정렬을 맞추기 위해 패딩이 들어가 크기가 24가 되지만, struct B는 char, char, double로 몰아서 16이 돼요.
결함 보고(Defect reports)
다음 동작 변경 결함 보고는 과거에 발표된 C 표준에 소급 적용됐어요.
| DR | 대상 | 발표된 동작 | 올바른 동작 |
|---|---|---|---|
| DR 499 | C11 | 익명 struct/union의 멤버가 감싸는 struct/union의 멤버로 간주됨 | 그것들은 자신의 메모리 배치를 유지함 |
더 알아보기
- struct 및 union 멤버 접근:
.과->연산자. - 비트 필드: 구조체 멤버에서 비트 단위로 저장 공간을 자르는 선언.
- 구조체 초기화: 구조체·union을 초기화하는 규칙.