기본 콘텐츠로 건너뛰기

방송대 C프로그래밍 12강: 구조체 전달과 공용체의 메모리 공유

0-썸네일-요약노트-C프로그래밍-12강

방송대 C프로그래밍 12강: 구조체 전달과 공용체의 메모리 공유

여러 멤버를 한꺼번에 함수로 보내려면 구조체 전체를 복사할 수도 있고 주소 하나만 전달할 수도 있습니다. 상태 몇 개만 저장한다면 비트필드로 폭을 줄일 수 있고, 여러 형 중 한 값만 사용할 때는 공용체로 저장공간을 공유할 수 있습니다. 이 글은 ‘복사·분할·공유’의 차이를 기준으로 세 문법을 선택하고 결과를 추적하도록 돕습니다.

먼저 결정할 것은 자료를 복사할지 원본에 닿을지이다

구조체형 자료를 함수에 전달하는 방법은 크게 두 가지입니다. 형식 매개변수를 구조체형으로 선언하면 구조체 전체가 복사되고, 구조체 포인터형으로 선언하면 구조체의 주소가 전달됩니다. 두 방식은 같은 계산 결과를 만들 수 있지만 원본 수정 가능성과 전달 비용이 다릅니다.

판단 기준값에 의한 구조체 전달구조체 포인터 전달
형식 매개변수struct num valuestruct num *p
함수로 넘어가는 것구조체 전체의 복사본원본 구조체의 주소
멤버 접근value.sump->sum
함수 안 수정의 대상복사본주소가 가리키는 원본
큰 구조체의 전달복사 시간과 별도 공간이 커질 수 있음구조체 크기와 관계없이 주소 하나를 전달

선택 기준: 원본을 보존한 독립 계산이 목적이면 값 전달이 이해하기 쉽습니다. 원본을 직접 갱신하거나 큰 구조체의 전체 복사를 피하려면 포인터 전달을 고려합니다.

값 전달에서는 계산한 복사본을 다시 받아야 한다

다음 예는 두 수와 합·곱을 한 구조체에 묶고, 구조체 전체를 함수에 전달합니다. 함수의 number는 main에 있는 data의 복사본입니다.

#include <stdio.h>

struct num {
    int x;
    int y;
    int sum;
    int mul;
};

struct num calc(struct num number)
{
    number.sum = number.x + number.y;
    number.mul = number.x * number.y;
    return number;
}

int main(void)
{
    struct num data = {10, 20, 0, 0};
    data = calc(data);
    printf("%d %d\n", data.sum, data.mul);
    return 0;
}

calc(data)가 실행되면 네 멤버가 number에 복사됩니다. 함수는 복사본의 sum과 mul을 30과 200으로 바꾸고 그 구조체를 반환합니다. 호출자가 data = calc(data);로 반환값을 다시 저장해야 원본 data에도 계산 결과가 남습니다.

오개념 교정: 함수 안에서 형식 매개변수의 멤버를 바꾸었다고 호출자의 구조체가 저절로 바뀌는 것은 아닙니다. 값 전달에서는 복사본이 바뀌므로, 수정된 구조체를 반환하고 호출자가 대입하는 통로가 필요합니다.

포인터 전달에서는 주소를 통해 같은 원본을 수정한다

구조체 포인터를 받으면 함수는 주소를 통해 호출자의 원본 멤버에 접근합니다. 다음 코드는 강의 예제의 계산을 포인터 전달 방식으로 나타냅니다.

#include <stdio.h>

struct num {
    int x;
    int y;
    int sum;
    int mul;
};

struct num *calc(struct num *number)
{
    number->sum = number->x + number->y;
    number->mul = number->x * number->y;
    return number;
}

int main(void)
{
    struct num data = {10, 20, 0, 0};
    calc(&data);
    printf("%d %d\n", data.sum, data.mul);
    return 0;
}

&data가 원본 구조체의 주소를 만들고, 형식 매개변수 number가 그 주소를 받습니다. number->sum은 (*number).sum과 같은 대상을 나타내므로 함수의 대입이 원본 data에 직접 반영됩니다.

C에서는 이때도 포인터 값, 즉 주소 자체가 형식 매개변수로 복사됩니다. 다만 복사된 주소를 통해 원본에 접근할 수 있기 때문에 강의에서는 이를 참조에 의한 구조체 전달 방식으로 구분합니다.

두 전달 방식은 원본 변화표로 구분하면 선명하다

함수 호출 전 원본의 sum과 mul이 0이라고 가정하고, 함수 안에서 두 멤버를 각각 30과 200으로 계산해 봅시다.

호출과 처리함수 안의 대상호출 직후 원본원본에 결과를 남기는 조건
calc(data)로 값 전달구조체 복사본sum=0, mul=0반환 구조체를 원본에 다시 대입
calc(&data)로 포인터 전달주소가 가리키는 원본sum=30, mul=200유효한 원본 주소를 전달

짧은 점검: 값 전달 함수의 호출을 calc(data);로만 쓰고 반환값을 버리면 원본의 계산 멤버는 그대로입니다. 반대로 포인터 전달 함수는 반환값을 사용하지 않아도 이미 원본을 수정했습니다. 결과가 어디에 기록되는지를 먼저 찾으면 두 호출을 혼동하지 않습니다.

비트필드는 멤버마다 필요한 비트 폭을 정한다

비트필드(bit field)는 구조체 멤버의 기억공간을 비트 단위로 지정하는 방법입니다. 적은 수의 상태값을 필요한 폭만큼 저장해 공간을 절약하거나, 여러 참·거짓 조건을 플래그로 표현할 때 사용할 수 있습니다.

struct flags {
    unsigned int ready : 1;
    unsigned int mode  : 2;
    unsigned int error : 1;
};

콜론 뒤 숫자가 각 멤버의 비트 크기입니다. ready와 error는 1비트, mode는 2비트를 사용합니다. 강의에서는 비트필드 멤버의 기본 자료형으로 int 또는 unsigned int를 제시하며, 다른 정수형의 허용과 실제 배치는 컴파일러 구현에 따라 달라질 수 있다고 설명합니다. 실수형, 포인터형, 사용자 정의형과 배열은 비트필드 멤버형으로 사용하지 않습니다.

비트 폭unsigned 멤버의 표현 가능한 값 수값의 범위
1비트2개0~1
2비트4개0~3
4비트16개0~15
7비트128개0~127

unsigned n비트 멤버는 0부터 2n-1까지 표현합니다. 따라서 2비트 멤버에 3은 들어가지만 4는 표현 범위를 벗어납니다. 폭은 단순 장식이 아니라 저장 가능한 상태의 수를 결정합니다.

비트 값은 이진수로 바꾸어 폭 안에 맞춘다

강의의 구조체는 각 멤버에 4비트, 2비트, 1비트, 7비트를 배정합니다.

struct test {
    unsigned short a : 4;
    unsigned short b : 2;
    unsigned short c : 1;
    unsigned short d : 7;
};

struct test bit = {3, 2, 1, 4};

각 값을 폭에 맞는 이진수로 쓰면 a=3은 4비트 0011, b=2는 2비트 10, c=1은 1비트 1, d=4는 7비트 0000100입니다. 합계는 14비트이므로 강의 도식의 16비트 접근 단위에서는 2비트가 사용되지 않습니다.

그 뒤 bit.a=10, bit.b=3, bit.c=0으로 바꾸면 폭에 맞춘 값은 각각 1010, 11, 0입니다. 멤버를 읽고 쓰는 문법은 일반 구조체처럼 점 연산자를 사용하지만, 각 값은 선언된 비트 폭 안에서 해석해야 합니다.

비트필드의 경계는 이름 없는 멤버로 조정한다

비트필드는 선언된 기반 자료형의 접근 단위 안에 배치됩니다. 강의의 저장 모형에서는 하나의 비트필드가 두 접근 단위에 걸쳐 놓이지 않고, 남은 공간에 들어가지 못하면 다음 단위로 이동합니다.

struct packed_flags {
    unsigned short a : 5;
    unsigned short   : 3;
    unsigned short b : 6;
    unsigned short c : 6;
    unsigned short d : 4;
};

이름이 없는 3비트 멤버는 값을 저장할 이름을 만들지 않고 a와 b 사이에 패딩을 둡니다. 폭이 0인 이름 없는 비트필드는 더 강한 경계 지시입니다.

struct next_unit {
    unsigned short a : 5;
    unsigned short   : 0;
    unsigned short b : 6;
    unsigned short c : 6;
    unsigned short d : 4;
};

: 0 뒤의 b는 현재 접근 단위의 남은 비트를 건너뛰고 다음 접근 단위에서 시작합니다. 또한 강의의 배치 예처럼 기반 자료형의 접근 단위 크기가 달라지면 새 단위가 시작될 수 있습니다. 실제 비트 순서와 전체 크기는 컴파일러 구현의 영향을 받으므로, 강의 도식은 규칙을 이해하는 배치 모형으로 읽어야 합니다.

오개념 교정: 비트 폭의 합이 21비트라고 해서 구조체가 반드시 정확히 21비트만 차지하는 것은 아닙니다. 접근 단위의 경계, 남은 공간, 이름 없는 패딩과 기반 자료형의 변화가 실제 저장 크기에 영향을 줍니다.

구조체는 멤버를 나란히 두고 공용체는 같은 자리를 겹쳐 쓴다

공용체(union)는 여러 멤버로 구성되지만 모든 멤버가 하나의 기억공간을 공유하는 사용자 정의 자료형입니다. 구조체가 각 멤버의 독립된 저장공간을 하나의 자료로 묶는 것과 대조됩니다.

union Var {
    char a;
    int b;
    double c;
};

union Var value;
비교 항목구조체공용체
멤버 저장공간각 멤버가 독립적으로 차지모든 멤버가 같은 공간을 공유
동시에 유지할 값여러 멤버의 값을 함께 유지한 시점에 사용하려는 한 멤버의 값
크기 판단멤버 배치와 정렬을 반영가장 큰 멤버를 저장할 수 있는 크기
객체 멤버 접근..
포인터 멤버 접근->->

강의 예에서는 char가 1바이트, int가 4바이트, double이 8바이트인 환경을 가정하므로 union Var는 가장 큰 double을 담을 수 있는 8바이트를 차지합니다. 세 멤버의 크기를 더한 13바이트가 되는 것이 아닙니다.

공용체에서는 마지막으로 쓴 멤버가 공유 공간의 상태를 정한다

공용체 멤버에 대입할 때마다 같은 기억공간의 비트가 새 자료형의 표현으로 다시 채워집니다.

union Var {
    char a;
    int b;
    double c;
};

union Var value;
value.a = 'A';
value.b = 66;
value.c = 1234.5678;
  1. value.a='A'는 공유 공간을 char형 값으로 사용합니다.
  2. value.b=66은 같은 공간을 int형 표현으로 다시 씁니다.
  3. value.c=1234.5678은 다시 double형 표현으로 덮어씁니다.

따라서 구조체처럼 세 값이 동시에 보존된다고 생각하면 안 됩니다. 마지막으로 저장한 멤버가 무엇인지 추적하고 그 멤버를 통해 값을 사용하는 것이 핵심입니다. 다른 멤버로 같은 비트를 읽은 수치는 환경의 자료 표현에 따라 달라질 수 있으므로 일반적인 계산 결과처럼 의존하지 않습니다.

공용체 객체에는 점 연산자를 쓰고, 공용체를 가리키는 포인터에는 화살표 연산자를 사용합니다.

union Var value;
union Var *p = &value;

p->a = 'A';
p->b = 66;
p->c = 1234.5678;

복사·분할·공유 중 목적에 맞는 메모리 전략을 고른다

상황우선 검토할 방식판단 이유
함수가 구조체 원본을 건드리지 않고 계산 결과를 새 값으로 만들기구조체 값 전달복사본을 처리하고 반환값으로 결과를 분리
큰 구조체의 멤버를 함수에서 직접 갱신하기구조체 포인터 전달주소 하나를 전달하고 원본에 접근
작은 상태값 여러 개를 제한된 비트 폭으로 표현하기구조체 비트필드멤버별 비트 수를 지정하고 플래그를 묶음
여러 자료형 가운데 한 시점에 한 값만 저장하기공용체가장 큰 멤버를 담을 공간을 여러 멤버가 공유

판단 절차: ① 값이 동시에 몇 개 살아 있어야 하는지, ② 함수가 원본을 수정해야 하는지, ③ 필요한 상태 수를 몇 비트로 표현할 수 있는지, ④ 배치가 컴파일러에 의존해도 되는지를 차례로 확인합니다.

핵심 개념 정리

  • 구조체 값 전달은 전체 복사본을 처리하므로 원본이 보존되며, 결과를 남기려면 반환 구조체를 다시 대입해야 합니다.
  • 구조체 포인터 전달은 주소를 통해 원본 멤버를 직접 수정하고 큰 구조체의 전체 복사를 피할 수 있습니다.
  • 비트필드는 멤버별 비트 폭을 정하지만 실제 배치에는 접근 단위, 패딩, 기반 자료형과 컴파일러 구현이 관여합니다.
  • 이름 없는 양의 폭 비트필드는 중간 패딩을 만들고, 폭이 0인 비트필드는 다음 접근 단위에서 후속 멤버를 시작하게 합니다.
  • 공용체의 모든 멤버는 하나의 기억공간을 공유하므로 멤버 대입이 바뀔 때 공유 공간의 상태도 덮어써집니다.
  • 구조체와 공용체 모두 객체에는 ., 포인터에는 ->로 멤버에 접근합니다.

이 강의를 관통하는 질문은 ‘기억공간을 누구와 어떻게 나누는가’입니다. 함수 전달에서는 원본과 복사본의 관계를, 비트필드에서는 한 접근 단위 안의 폭과 경계를, 공용체에서는 같은 공간을 마지막으로 쓴 멤버를 표시하세요. 그러면 문법을 따로 외우지 않고 복사·분할·공유라는 세 메모리 전략으로 연결할 수 있습니다.

예상문제 10선

1. 구조체 값 전달과 포인터 전달의 차이를 바르게 설명한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 구조체형 매개변수에는 전체 값이 복사되고 구조체 포인터형 매개변수에는 원본을 가리키는 주소가 복사됩니다.
  • ② 오답: 원본에 직접 닿는 쪽은 주소를 받은 포인터 전달이며 설명이 반대입니다.
  • ③ 오답: 값 전달은 모든 멤버를 포함한 구조체 전체를 복사하고 포인터 전달은 구조체의 주소를 전달합니다.
  • ④ 오답: 값 방식은 구조체형과 점 연산자, 포인터 방식은 구조체 포인터형과 화살표 연산자를 사용합니다.

2. 값 전달 함수 struct num calc(struct num n)가 n의 멤버를 수정해 반환한다. 원본 data에 결과를 남기는 호출은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 함수의 매개변수는 구조체형이므로 구조체 포인터인 &data와 형이 맞지 않습니다.
  • ② 오답: 주소를 구하는 식은 구조체 반환값을 저장할 대입 대상이 될 수 없습니다.
  • ③ 오답: data는 포인터가 아니므로 별표로 간접 참조할 수 없습니다.
  • ④ 정답: 함수가 수정한 복사본을 구조체 값으로 반환하므로 그 값을 원본에 다시 대입해야 합니다.

3. 함수에서 구조체 포인터 p가 가리키는 원본의 sum 멤버를 30으로 바꾸는 표현은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 점 연산자는 구조체 객체에 적용하며 p 자체는 구조체가 아니라 포인터입니다.
  • ② 정답: 화살표 연산자는 구조체 포인터를 간접 참조한 뒤 해당 멤버를 선택합니다.
  • ③ 오답: p에 점 연산자를 적용할 수 없고 주소식은 정수 30의 대입 대상도 아닙니다.
  • ④ 오답: 연산 순서를 명확히 하려면 (*p).sum이어야 하며 제시된 식은 그 의미가 아닙니다.

4. 비트필드의 주된 용도로 가장 적절한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: 함수 전달 방식은 구조체형 또는 구조체 포인터형 매개변수로 결정하며 비트필드의 역할이 아닙니다.
  • ② 오답: 강의에서 실수형은 비트필드 멤버형으로 사용할 수 없는 대상으로 구분합니다.
  • ③ 정답: 멤버별 비트 수를 지정하면 제한된 상태값을 저장하고 플래그를 묶는 데 활용할 수 있습니다.
  • ④ 오답: 비트필드는 공용체의 어떤 멤버가 마지막으로 저장되었는지 별도로 기록하지 않습니다.

5. unsigned 4비트 비트필드 멤버가 표현할 수 있는 값의 범위는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 4비트는 2의 4제곱인 16가지 조합을 만들므로 unsigned 값은 0부터 15까지입니다.
  • ② 오답: 비트 수 4를 최댓값으로 착각했으며 비트 폭은 조합 수를 지수적으로 결정합니다.
  • ③ 오답: unsigned 범위에는 0이 포함되고 16은 다섯 번째 비트가 필요합니다.
  • ④ 오답: 0부터 16까지는 17가지 값이므로 4비트의 16가지 조합을 넘습니다.

6. 비트필드에서 이름 없이 폭을 0으로 지정한 멤버의 효과는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 폭 0은 값 초기화 문법이 아니라 저장 배치의 경계를 지정하는 문법입니다.
  • ② 정답: 현재 접근 단위의 남은 비트를 건너뛰어 뒤 멤버가 다음 단위에서 배치되게 합니다.
  • ③ 오답: 비트 공유는 공용체의 메모리 개념이며 폭 0은 오히려 현재 단위와 경계를 나눕니다.
  • ④ 오답: 구조체의 저장공간을 없애는 기능이 아니라 후속 비트필드의 시작 위치를 조정합니다.

7. 구조체와 공용체의 기억공간 차이를 바르게 설명한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 같은 공간을 겹쳐 쓰는 것은 공용체이며 구조체의 멤버는 독립적으로 배치됩니다.
  • ② 오답: 가장 큰 멤버를 담을 공유 공간을 두는 쪽은 구조체가 아니라 공용체입니다.
  • ③ 오답: 독립 공간에서 여러 값을 함께 보존하는 설명은 구조체에 해당합니다.
  • ④ 정답: 두 자료형을 나누는 핵심은 여러 멤버의 저장공간이 독립인지 공유인지입니다.

8. 공용체 변수에 u.a='A'; u.b=66; u.c=1.5;를 차례로 실행했다. 공유 공간을 마지막으로 채운 멤버는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: a가 먼저 공간을 사용했지만 뒤의 b와 c 대입이 같은 공간을 다시 썼습니다.
  • ② 오답: b 대입 뒤에 c 대입이 있으므로 b의 표현이 마지막 상태가 아닙니다.
  • ③ 정답: 공용체는 같은 공간을 공유하며 마지막 문장이 c의 double형 값으로 그 공간을 채웁니다.
  • ④ 오답: 독립된 세 저장공간을 갖는 것은 구조체의 방식이며 공용체와 다릅니다.

9. char 1바이트, int 4바이트, double 8바이트인 강의의 환경에서 세 멤버를 가진 union Var의 크기는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: 가장 작은 char만 담을 크기로는 int와 double 멤버를 저장할 수 없습니다.
  • ② 오답: int는 담을 수 있지만 8바이트인 double을 담기에 부족합니다.
  • ③ 정답: 공유 공간은 가장 큰 멤버인 double을 저장할 수 있어야 하므로 제시된 환경에서는 8바이트입니다.
  • ④ 오답: 13은 세 멤버 크기의 합이며 공용체는 멤버별 독립 공간을 만들지 않습니다.

10. 상황과 자료 표현 방식의 연결로 가장 적절한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 값 전달은 복사본을 만들므로 원본 직접 갱신과 큰 구조체의 복사 회피라는 조건에 맞지 않습니다.
  • ② 오답: 공용체의 연속 대입은 같은 공간을 덮어쓰므로 여러 형의 값을 동시에 보존하지 못합니다.
  • ③ 오답: 제한된 작은 상태는 필요한 폭을 명시하는 비트필드의 활용 목적과 잘 맞습니다.
  • ④ 정답: 수정 대상, 필요한 상태 수, 동시에 살아 있어야 할 값의 수를 기준으로 세 방식을 올바르게 연결했습니다.

참고 자료와 작성 기준

이 글은 해당 차시 강의자료를 바탕으로 학습 목적에 맞게 재구성한 비공식 학습자료입니다. 전달 방식 비교, 비트 폭 계산과 공용체 상태 추적은 학습자의 이해를 돕도록 구성하고 검토했습니다.

  • 작성·편집: 올에이클래스 학습연구팀
  • 주요 근거: 한국방송통신대학교 C프로그래밍 12강 「구조체와 공용체(2)」 강의자료(2025)
  • 보충 자료: 외부 자료를 사용하지 않았으며 강의자료의 개념 범위 안에서 재구성
  • 편집 원칙: 올에이클래스 편집 정책
  • 최종 내용 검토: 2026-09-07

댓글