방송대 C프로그래밍 11강: 구조체의 선언·메모리 배치와 포인터 접근
이름·나이·키를 서로 떨어진 변수로 관리하면 “한 사람의 정보”라는 관계가 코드에 드러나지 않는다. 구조체는 자료형이 다른 값들을 하나의 레코드로 묶고, 배열과 포인터로 그 레코드를 반복 처리하게 해 준다. 선언문을 읽는 법부터 멤버의 실제 배치, .과 ->를 고르는 기준까지 한 학생 정보가 이동하는 과정으로 추적해 보자.
흩어진 세 값보다 ‘학생 한 명’이라는 단위가 먼저다
학생 한 명을 이름, 나이, 키로 표현한다고 하자. char name[20], int age, float height는 자료형이 서로 다르다. 이들을 별도 변수로 두면 값은 저장할 수 있지만, 어느 이름과 어느 나이와 어느 키가 같은 학생의 것인지 프로그래머가 계속 맞춰야 한다. 학생이 여러 명이면 이름 배열, 나이 배열, 키 배열의 같은 인덱스를 유지해야 하는 부담도 생긴다.
구조체(structure)는 관련된 여러 자료를 모아 하나의 새로운 자료형을 정의한다. 그 안의 각 자료를 멤버(member)라고 하며 멤버마다 이름을 붙인다. 배열이 같은 자료형의 값을 반복해서 담는 동형 자료구조라면, 구조체는 서로 다른 자료형도 한 단위로 묶을 수 있는 이형 자료구조다.
struct Student {
char name[20];
int age;
float height;
};
이 코드는 아직 특정 학생의 값을 저장한 것이 아니다. struct Student라는 자료형의 설계도를 정의한 것이다. 설계도에는 문자 배열 name, 정수 age, 실수 height가 어떤 이름과 순서로 들어가는지가 적혀 있다.
첫 번째 구분: 구조체 정의는 “어떤 멤버를 가진 자료형인가”를 정하고, 구조체 변수 선언은 “그 자료형의 값을 저장할 실제 객체를 몇 개 만들 것인가”를 정한다.
정의·변수 선언·typedef는 서로 다른 이름을 만든다
구조체 문법이 길어 보이는 이유는 구조체 태그, 변수 이름, 자료형 별칭이 한 코드에 함께 등장할 수 있기 때문이다. 각각이 붙이는 이름의 대상을 분리하면 선언을 정확히 읽을 수 있다.
| 코드의 이름 | 이름이 가리키는 것 | 사용 예 |
|---|---|---|
Student (태그) | 구조체 설계도의 이름 | struct Student s1; |
s1 (변수) | 설계도에 따라 만들어진 한 객체 | s1.age = 21; |
Student (별칭) | typedef로 붙인 자료형의 새 이름 | Student s2; |
태그가 있는 구조체는 정의와 동시에 변수를 선언할 수도 있고, 정의를 끝낸 뒤 별도로 변수를 선언할 수도 있다. 두 방식으로 만든 변수의 자료형과 기능은 같다.
struct Circle {
float x, y;
float radius;
} c1;
struct Circle c2;
c1은 닫는 중괄호 뒤에서 즉시 선언했고, c2는 이미 정의된 struct Circle을 사용해 나중에 선언했다. 여기서 닫는 중괄호 뒤와 전체 선언 끝의 세미콜론을 빠뜨리지 않아야 한다.
typedef는 기존 자료형에 새 이름을 붙인다. 구조체와 결합하면 변수를 선언할 때마다 struct를 반복하지 않아도 된다.
typedef struct {
char name[20];
int age;
float height;
} Student;
Student s1;
Student group[3];
Student는 변수가 아니라 자료형의 별칭이다. s1은 그 자료형의 변수이고, group은 그 자료형의 요소 세 개를 가진 배열이다. typedef가 새로운 기억공간을 만드는 것은 아니며 기존 자료형을 더 간결하게 쓰도록 이름만 추가한다.
선언문 판독 장치: 중괄호 안에서는 멤버 이름을, 닫는 중괄호 뒤에서는 자료형 별칭이나 변수 이름을 찾는다. 그 이름이 이후 선언의 자료형 자리에 오는지, 값이 저장되는 변수 자리에 오는지를 확인하면 태그·별칭·객체를 혼동하지 않는다.
초기화 목록은 멤버 선언 순서와 일대일로 대응한다
구조체 변수는 선언과 동시에 중괄호로 초기화할 수 있다. 초깃값은 구조체 정의에 적힌 멤버 순서대로 대응한다.
Student minsu = {"김민수", 22, 175.5f};
첫 값 "김민수"는 name, 두 번째 값 22는 age, 세 번째 값 175.5f는 height에 들어간다. 초깃값 목록에는 멤버 이름이 보이지 않으므로 정의의 순서를 바꾸면 같은 목록의 의미도 달라질 수 있다.
값을 넣기 전에 ‘자료형-순서-단위’를 점검한다
- 자료형: 각 초깃값이 대응 멤버의 자료형에 맞는지 확인한다.
- 순서: 목록의 첫 값부터 구조체 정의의 첫 멤버에 차례로 연결한다.
- 단위: 하나의 중괄호 묶음이 구조체 객체 한 개를 나타내는지 확인한다.
이 원리는 구조체 배열에서 더 분명해진다. 바깥 중괄호는 배열 전체, 안쪽 중괄호 하나는 학생 한 명을 뜻한다.
Student class_a[3] = {
{"김민수", 22, 175.5f},
{"이서연", 20, 162.0f},
{"박준호", 24, 181.2f}
};
class_a[0]은 김민수의 세 멤버를 한꺼번에 가진 첫 객체이고, class_a[1]과 class_a[2]도 각각 독립된 구조체 객체다. 이름·나이·키가 분리된 세 배열을 병렬로 맞추는 대신, 학생 단위로 묶인 레코드를 배열 요소로 반복할 수 있다.
점 연산자는 객체에서 멤버 하나를 선택한다
구조체 변수 자체를 가지고 있을 때는 구조체변수.멤버 형식으로 멤버를 참조한다. 점 왼쪽에는 객체가, 오른쪽에는 그 구조체 정의에 존재하는 멤버 이름이 온다.
#include <stdio.h>
typedef struct {
char name[20];
int age;
float height;
} Student;
int main(void) {
Student s = {"김민수", 22, 175.5f};
s.age = s.age + 1;
printf("%s, %d세, %.1fcm\n", s.name, s.age, s.height);
return 0;
}
s.age를 읽어 1을 더한 뒤 같은 멤버에 저장하므로 나이는 23이 된다. 출력은 김민수, 23세, 175.5cm다. 하나의 객체 안에서도 각 멤버는 자신의 자료형에 맞는 서식을 사용한다. 문자 배열은 %s, 정수는 %d, 실수 값은 %.1f로 출력했다.
배열 요소도 먼저 객체를 고른 뒤 멤버를 고른다
class_a[i].height는 두 번 선택하는 표현이다. 먼저 [i]로 배열의 i번째 구조체 객체를 고르고, 그 객체에서 .으로 height 멤버를 고른다. 따라서 class_a[1].age는 두 번째 학생의 나이다.
접근식 읽기: class_a[i].height를 오른쪽부터 외우지 말고 “배열에서 i번째 학생 선택 → 그 학생의 height 선택”이라는 왼쪽에서 오른쪽의 선택 과정으로 읽는다.
구조체 크기는 멤버 크기의 단순 합과 다를 수 있다
구조체의 멤버는 선언 순서에 따라 기억공간에 놓이지만, 컴파일러는 각 멤버를 효율적으로 접근할 수 있도록 특정 바이트 경계에 맞춘다. 이 정렬 과정에서 멤버 사이 또는 구조체 끝에 사용하지 않는 패딩(padding) 바이트가 추가될 수 있다.
typedef struct {
int a;
char b;
int c;
} StructEx;
강의의 실행 환경에서는 int가 4바이트이고 이 구조체의 크기가 12바이트로 제시된다. a가 4바이트, b가 1바이트를 사용한 뒤, 다음 int c를 적절한 경계에서 시작하기 위해 b와 c 사이에 3바이트 패딩이 들어간다. 따라서 4 + 1 + 3 + 4 = 12가 된다.
| 논리 구간 | 강의 예시의 크기 | 역할 |
|---|---|---|
a | 4바이트 | 첫 번째 정수 멤버 저장 |
b | 1바이트 | 문자 멤버 저장 |
| 패딩 | 3바이트 | 다음 정수 멤버의 시작 위치를 정렬 |
c | 4바이트 | 두 번째 정수 멤버 저장 |
중요한 점은 모든 환경에서 무조건 12바이트라고 외우는 것이 아니다. 자료형의 크기와 정렬 규칙은 구현 환경에 따라 달라질 수 있으므로 실제 크기는 sizeof로 확인해야 한다. 강의 예시는 “멤버 크기의 합이 곧 구조체 크기”라는 오개념을 고치는 사례로 사용한다.
StructEx x = {10, 'A', 20};
printf("sizeof(x) = %zu\n", sizeof(x));
강의 환경의 출력은 sizeof(x) = 12다. 패딩에도 불구하고 멤버 접근식은 그대로 x.a, x.b, x.c를 사용한다. 프로그래머가 패딩 위치를 직접 계산해 주소를 옮기는 것이 아니라, 컴파일러가 정한 배치에 따라 멤버 이름으로 접근한다.
구조체 배열은 ‘행 선택 후 열 선택’으로 순회한다
학생 명부를 표로 생각하면 구조체 배열의 각 요소가 한 행이고, 멤버가 그 행의 열이다. 반복문은 행을 바꾸고 점 연산자는 현재 행의 열을 고른다.
#include <stdio.h>
typedef struct {
char name[20];
int age;
float height;
} Student;
int main(void) {
Student class_a[3] = {
{"김민수", 22, 175.5f},
{"이서연", 20, 162.0f},
{"박준호", 24, 181.2f}
};
float total = 0.0f;
for (int i = 0; i < 3; i++) {
total += class_a[i].height;
}
printf("평균 키 = %.1fcm\n", total / 3);
return 0;
}
반복 추적에서 i=0이면 175.5, i=1이면 162.0, i=2이면 181.2를 더한다. 합은 518.7이고 3으로 나누면 172.9이므로 출력은 평균 키 = 172.9cm다. 배열의 경계는 이전과 마찬가지로 0 이상 3 미만이며, class_a[3]은 존재하지 않는 네 번째 객체다.
오류를 찾을 때 두 경계를 따로 본다
- 배열 경계: 인덱스가 실제 객체 수 안에 있는가?
- 멤버 경계: 선택한 멤버가 구조체 정의에 있고 자료형에 맞게 사용되는가?
예를 들어 class_a[1].score는 인덱스는 유효하지만 Student에 score 멤버가 없어 잘못됐다. 반대로 class_a[3].age는 age라는 멤버는 존재하지만 배열 범위를 벗어난다. 한 표현 안에서도 오류의 층위가 다르다.
포인터가 들고 있는 것은 구조체 값이 아니라 객체의 주소다
구조체 포인터는 구조체 객체가 놓인 주소를 저장한다. 기본 자료형 포인터와 선언 원리는 같으며, 자료형 자리에 구조체 자료형을 쓴다.
Student s = {"김민수", 22, 175.5f};
Student *p = &s;
s는 구조체 객체이고 &s는 그 객체의 주소다. p는 Student를 가리킬 수 있는 포인터이므로 그 주소를 저장한다. 포인터를 통해 멤버를 고르는 방법은 두 가지이며 의미는 같다.
printf("%d\n", (*p).age);
printf("%d\n", p->age);
두 문장 모두 22를 출력한다. 첫 표현은 *p로 주소가 가리키는 구조체 객체를 얻은 뒤 .age로 멤버를 선택한다. 점 연산자의 우선순위가 간접 참조 연산자 *보다 높으므로 (*p).age처럼 괄호가 필요하다. p->age는 이 과정을 간결하게 표현하는 구조체 포인터 전용 멤버 접근식이다.
| 왼쪽 표현의 종류 | 멤버 접근식 | 읽는 순서 |
|---|---|---|
구조체 객체 s | s.age | 객체 s에서 age 선택 |
구조체 포인터 p | (*p).age | p가 가리키는 객체를 얻고 age 선택 |
구조체 포인터 p | p->age | 포인터가 가리키는 객체의 age 선택 |
p.age와 *p.age가 틀리는 이유
p.age는 점 왼쪽의 p가 구조체 객체가 아니라 포인터이므로 맞지 않는다. *p.age는 연산자 우선순위 때문에 *(p.age)로 해석하려 하지만, 역시 포인터 p에 점 연산자를 적용하려 하므로 잘못된다. 포인터라면 p->age, 객체라면 s.age라는 선택 기준을 먼저 적용해야 한다.
구조체 표현은 네 번의 선택으로 검토한다
정의부터 포인터 접근까지 한꺼번에 보이면 기호를 외우기 쉽다. 그러나 다른 구조체에도 재사용할 수 있는 판독 순서를 만들면 선언과 접근 오류를 스스로 찾을 수 있다.
- 묶을 단위 선택: 함께 움직여야 하는 값이 무엇인지 정하고 각 멤버의 자료형과 이름을 적는다.
- 이름의 역할 선택: 구조체 태그인지,
typedef별칭인지, 값을 저장하는 변수인지 구분한다. - 객체 위치 선택: 단일 변수인지 배열의 i번째 객체인지, 포인터가 가리키는 객체인지 확인한다.
- 접근 연산자 선택: 객체에는
., 포인터에는->를 쓰고, 초기화는 멤버 선언 순서와 맞춘다.
예를 들어 Student *p = &class_a[1];에서 Student는 자료형 별칭, p는 포인터 변수, class_a[1]은 두 번째 구조체 객체다. p->height는 바로 그 객체의 키를 선택하므로 값은 162.0이다. 같은 위치를 class_a[1].height로도 선택할 수 있지만 출발점이 객체인지 포인터인지에 따라 연산자가 달라진다.
자가 점검: 코드를 읽을 때 각 식의 왼쪽에 ‘객체’, ‘배열 요소’, ‘포인터’ 중 하나를 써 보자. 이어서 선택한 멤버의 자료형과 배열 인덱스 범위를 표시하고, 구조체 크기 문제에서는 멤버 합 대신 sizeof와 정렬·패딩 가능성을 확인한다.
핵심 개념 정리
- 구조체 정의는 서로 다른 자료형의 관련 값을 멤버로 묶어 사용자 정의 자료형의 설계도를 만든다.
- 구조체 태그,
typedef별칭, 구조체 변수는 각각 설계도 이름, 자료형 이름, 실제 값을 저장하는 객체라는 서로 다른 역할을 가진다. - 중괄호 초기화 목록은 멤버 선언 순서대로 대응하며, 구조체 배열에서는 안쪽 중괄호 하나가 객체 하나를 나타낸다.
- 구조체 객체의 멤버는
.으로, 구조체 포인터가 가리키는 객체의 멤버는->로 선택한다. p->member는(*p).member와 같은 뜻이고, 후자에는 연산자 우선순위 때문에 괄호가 필요하다.- 구조체 배열은 인덱스로 객체를 먼저 고른 뒤 점 연산자로 멤버를 고르며, 배열 경계와 멤버 존재 여부를 별도로 확인한다.
- 구조체에는 정렬을 위한 패딩이 들어갈 수 있으므로 실제 크기는 멤버 크기의 단순 합으로 단정하지 않고
sizeof로 확인한다.
구조체 문제를 만나면 먼저 함께 움직일 값을 하나의 객체로 묶고, 선언에서 태그·별칭·변수의 역할을 표시한다. 그다음 초기화 값을 멤버 순서에 연결하고, 식의 출발점이 객체인지 배열 요소인지 포인터인지 확인해 . 또는 ->를 고른다. 마지막으로 배열 범위와 정렬·패딩을 따로 점검하면 선언, 접근, 크기 문제를 같은 사고 순서로 풀 수 있다.
예상문제 10선
1. 구조체에 대한 설명으로 가장 알맞은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: 같은 자료형의 반복 저장은 배열의 특징이며 구조체 멤버는 서로 다른 자료형일 수 있다.
- ② 오답: 포인터는 주소를 저장하지만 구조체는 관련 값을 하나의 객체로 묶는 자료형이다.
- ③ 정답: 구조체는 이름이 붙은 멤버들을 모아 새로운 자료형을 정의한다.
- ④ 오답: 구조체는 자료 표현 방법이며 함수 반환 흐름을 자동 제어하지 않는다.
2. typedef struct { int id; } Student;에서 Student의 역할은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답:
id에 들어갈 값은 변수를 초기화할 때 별도로 지정한다. - ② 정답:
typedef는 기존 자료형을 이후 선언에서 사용할 별칭을 만든다. - ③ 오답: 별칭만 정의했으므로 실제 변수는
Student s;처럼 따로 선언해야 한다. - ④ 오답: 포인터가 되려면 변수 선언에
*가 있어야 하며Student자체는 자료형 이름이다.
3. Student s = {"한나", 21, 165.0f};를 멤버 선언 순서가 name, age, height일 때 올바르게 연결한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답: 첫 값은 첫 멤버에 대응하므로 문자열과 정수의 자리를 바꿀 수 없다.
- ② 오답: 중괄호 초기화는 자료형을 검색해 배치하지 않고 선언 순서를 따른다.
- ③ 오답: 멤버 이름의 알파벳순은 초기화 순서와 관계없다.
- ④ 정답: 세 초깃값이 정의에 적힌
name, age, height순서대로 일대일 대응한다.
4. 구조체 변수 Student s;의 age 멤버에 25를 저장하는 올바른 문장은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: 왼쪽의
s가 구조체 객체이므로 점 연산자로 멤버를 선택한다. - ② 오답: 화살표 연산자는 구조체 포인터가 가리키는 객체의 멤버에 접근할 때 쓴다.
- ③ 오답:
Student는 자료형 이름이며 값을 저장하는 객체가 아니다. - ④ 오답: 대괄호는 배열의 인덱스를 선택하며 멤버 이름 접근에는 사용하지 않는다.
5. Student *p = &s; 뒤에 포인터를 통해 나이를 30으로 바꾸는 올바른 문장은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답:
p는 구조체 포인터이므로->로 가리키는 객체의 멤버를 선택한다. - ② 오답: 점 연산자의 왼쪽에는 구조체 객체가 와야 하지만
p는 주소를 저장한 포인터다. - ③ 오답: 점 연산자가
*보다 먼저 결합해p.age를 시도하므로 올바른 간접 참조가 아니다. - ④ 오답: 주소 연산 결과는 대입할 멤버 객체가 아니며 포인터 멤버 접근식도 빠져 있다.
6. 강의 환경에서 int a; char b; int c; 순서의 구조체 크기가 12바이트인 이유는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: 강의 예시에서
char멤버는 1바이트이며 나머지 공간은 값 저장이 아닌 패딩이다. - ② 오답: 패딩의 위치와 크기는 멤버 구성과 구현의 정렬 규칙에 따라 달라진다.
- ③ 정답: 4+1바이트 뒤에 3바이트를 비워 다음 4바이트 정수의 시작 경계를 맞춰 총 12바이트가 된다.
- ④ 오답: 구조체의 크기는 저장한 값의 산술 합이 아니라 자료형 크기와 배치로 결정된다.
7. Student class_a[3]에서 두 번째 학생의 키를 선택하는 표현은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답: 인덱스 2는 0부터 세었을 때 세 번째 학생을 선택한다.
- ② 정답: 인덱스 1로 두 번째 구조체 객체를 고른 뒤 점 연산자로
height를 선택한다. - ③ 오답:
class_a는 배열 전체이므로 먼저 대괄호로 구조체 요소를 선택해야 한다. - ④ 오답:
class_a[1]은 포인터가 아니라 구조체 객체이므로 점 연산자를 사용한다.
8. 구조체 포인터 p에 대해 p->age와 같은 의미의 표현은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답: 우선순위 때문에 먼저
p.age를 시도하므로 포인터가 가리키는 객체를 올바르게 선택하지 못한다. - ② 오답: 주소 연산자는 멤버 접근을 대신하지 않으며 점 연산자의 왼쪽도 포인터다.
- ③ 오답:
p는 구조체 객체가 아니라 포인터이므로 점 연산자를 직접 적용할 수 없다. - ④ 정답: 괄호 안에서 먼저
p를 역참조해 객체를 얻고 그 객체의age를 선택한다.
9. 구조체의 기억공간에 대한 설명으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: 멤버 사이 또는 끝에 패딩이 들어가면 단순 합보다 구조체 크기가 커질 수 있다.
- ② 오답: 패딩은 정렬을 위한 빈 공간이며 멤버 값을 저장하는 칸이 아니다.
- ③ 정답: 자료형 크기와 정렬 규칙이 구현에 따라 달라질 수 있어
sizeof가 실제 크기를 알려 준다. - ④ 오답: 컴파일러가 배치를 처리하므로 패딩 여부와 관계없이 멤버 이름과 점 또는 화살표 연산자를 쓴다.
10. 처음 보는 구조체 코드를 검토하는 순서로 가장 적절한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답: 패딩 때문에 크기를 단순 합으로 고정할 수 없고 포인터 접근에는 화살표가 필요하다.
- ② 정답: 자료의 묶음과 이름 역할을 먼저 정한 뒤 출발점에 맞는 접근식과 배열·메모리 경계를 확인한다.
- ③ 오답: 객체에는 점, 포인터에는 화살표를 쓰며 초기화 순서와 0부터 시작하는 배열 경계를 지켜야 한다.
- ④ 오답: 초깃값은 선언 순서를 따르고 패딩은 값 저장용이 아니며
typedef는 자료형 별칭을 만든다.
참고 자료와 작성 기준
이 글은 해당 차시 강의자료를 바탕으로 학습 목적에 맞게 재구성한 비공식 학습자료입니다. 학생 명부 레코드의 생성·배열 순회·포인터 접근을 잇는 사례, 태그·별칭·변수 판독표, 패딩 계산과 객체 위치에 따른 연산자 선택 절차는 학습자가 직접 코드를 판독하도록 새로 구성했습니다.
- 작성·편집: 올에이클래스 학습연구팀
- 주요 근거: 한국방송통신대학교 컴퓨터과학과, 「C프로그래밍」 11강 구조체와 공용체(1) 강의자료, 2025
- 보충 자료: 외부 자료를 사용하지 않음
- 편집 원칙: 올에이클래스 편집 정책
- 최종 내용 검토: 2026-09-07
댓글
댓글 쓰기