구조체(struct)란?

C 언어에서 형이 다른 여러 값(멤버)을 한데 묶어 하나의 형으로 다루는 구조. struct sensor { int id; float value; };처럼 정의하며, 변수 s의 멤버는 s.id, 구조체에 대한 포인터 p에서는 p->id로 사용한다. 멤버는 선언한 순서대로 메모리에 나열되지만, 정렬(alignment) 때문에 사이에 채움 바이트(패딩)가 들어가는 경우가 있어 전체 크기가 멤버 크기의 합계보다 커지기도 한다.

일본어 명칭: 構造体

구조체(struct)은(는) ISA와 어셈블리, C 언어 기초, 메모리 계층 구조 등 7개 분야의 연습 문제 11개에 나와요.

분야: C 언어 기초

정의와 사용법

struct point { int x; int y; };로 정의하면 struct point p = {1, 2};처럼 변수를 만들고, p.x나 p.y로 각 멤버를 사용할 수 있습니다. typedef로 typedef struct { … } Point;처럼 별명을 붙이면 struct를 쓰지 않고 Point p;로 선언할 수 있습니다. 구조체끼리는 =로 통째로 대입할 수 있고 함수의 인수나 반환값으로도 쓸 수 있지만, 큰 구조체는 포인터로 전달하는 것이 일반적입니다.

메모리에서의 배치

멤버는 선언한 순서대로 주소가 작은 쪽부터 놓입니다. int처럼 4바이트 경계에 맞춰야 하는 형 앞에는 채움 바이트(패딩)가 들어가며, 구조체 전체의 크기도 가장 엄격한 정렬 단위의 배수로 올림됩니다. 따라서 char, int, char 순서로 나열한 구조체는 int가 4바이트인 일반적인 환경에서 12바이트가 되며, int를 먼저 두면 8바이트로 줄어듭니다.

임베디드나 통신에서의 주의점

주변 회로의 레지스터 배치를 구조체로 나타내는 일은 흔하지만, 패딩이나 비트 필드의 배치 순서는 처리계에 따라 달라도 되므로 확인하지 않고 사용하면 주소가 어긋납니다. 통신 데이터나 저장 파일의 형식을 구조체의 메모리 내용 그대로 다루면, 패딩이나 엔디언의 차이로 다른 환경과 호환성이 없어지므로 1바이트씩 조립하는 방법이 자주 사용됩니다.

도전해 보기: 구조체(struct) 관련 문제

C 언어의 비트 필드를 사용한 구조체로 주변 회로의 레지스터를 나타낼 때 주의가 필요한 점으로 올바른 것은 무엇입니까?

  1. A비트 필드를 사용하면 프로그램이 반드시 2배 빨라진다
  2. B비트 필드의 비트 배열 순서 등은 컴파일러마다 달라도 되므로, 다른 컴파일러에서는 의도와 다른 비트를 조작하는 경우가 있다
  3. C비트 필드는 8비트 이상의 값을 나타낼 수 없다
정답과 해설 보기

정답: B. 비트 필드의 비트 배열 순서 등은 컴파일러마다 달라도 되므로, 다른 컴파일러에서는 의도와 다른 비트를 조작하는 경우가 있다

비트 필드는 읽기 쉽게 쓸 수 있지만, C 언어 규격에서는 구조체 안에서 비트를 상위부터 배치할지 하위부터 배치할지, 필드가 경계를 넘을 때의 취급, 접근 폭 등이 처리계에 맡겨져 있습니다. 그래서 어떤 컴파일러에서 올바르게 동작해도 다른 컴파일러나 CPU에서는 다른 비트를 읽고 쓸 수 있습니다. 마이크로컨트롤러 제조사가 제공하는 헤더는 마스크와 시프트로 작성된 경우가 많습니다.

자주 묻는 질문

구조체와 배열의 차이는 무엇인가요?

배열은 같은 형의 요소를 나열한 것으로, 요소는 인덱스로 고릅니다. 구조체는 형이 다른 멤버를 묶은 것으로, 멤버는 이름으로 고릅니다. 구조체의 배열이나 배열을 멤버로 갖는 구조체처럼 조합해서 사용할 수도 있습니다.

구조체의 크기를 sizeof로 확인해야 하는 이유는 무엇인가요?

멤버 사이나 끝에 들어가는 패딩의 양은 형의 크기와 정렬 규칙(ABI)에 따라 달라지므로, 멤버의 크기를 더하기만 해서는 올바른 크기가 되지 않습니다. malloc으로 구조체의 영역을 확보할 때나 파일에 쓸 때는 sizeof(struct 이름)로 실제 크기를 구합니다.

해설 속 구조체(struct)

구조체(struct)이(가) 나오는 문제 (11)

  1. 위와 같이 x 좌표만 합산하는 처리를 배열의 구조체(각 성분을 별도의 배열로 만드는 형태)로 바꾸면 빨라지기 쉬운 이유는?
  2. C 언어의 비트 필드를 사용한 구조체로 주변 회로의 레지스터를 나타낼 때 주의가 필요한 점으로 올바른 것은 무엇입니까?
  3. RISC-V 명령어 'lw t0, 8(sp)'의 동작으로 올바른 것은?
  4. RISC-V의 표준 호출 규약에서 정수 인자가 10개인 함수를 호출할 때 9번째와 10번째 인자는 어디로 전달되는가?
  5. RISC-V에서 4바이트를 읽는 lw 명령어의 주소가 4의 배수가 아닐 때(경계가 맞지 않을 때) 일어날 수 있는 일로 올바른 것은?
  6. ABI(애플리케이션 바이너리 인터페이스)에 대한 설명으로 올바른 것은?
  7. Linux 공유 라이브러리에서 libfoo.so.1의 '.1'과 같은 soname 버전 번호를 올리는 것은 일반적으로 어떤 경우입니까?
  8. 스레드별 카운터를 'long cnt[8];'처럼 늘어놓은 배열에서 거짓 공유가 일어나고 있다. 대책으로 가장 적절한 것은 무엇입니까?
  9. 입자의 위치 x, y, z를 가진 데이터를 GPU에서 처리할 때, 메모리 병합 접근 측면에서 유리한 배치는?
  10. char는 1바이트, int는 4바이트이며, int를 4의 배수 주소에 맞춰 두는 일반적인 환경으로 한다. 위의 코드를 실행했을 때의 출력으로 올바른 것은?
  11. 헤더 파일 sensor.h를 위와 같이 작성했다. #ifndef, #define, #endif의 세 줄의 목적으로 올바른 것은?

관련 용어

배열(Array) SIMD 스택(Stack) 변수(Variable) 호출 규약(Calling Convention) 포인터(Pointer) 정렬과 패딩(Alignment and Padding) 벡터화(vectorization) 비트 필드(Bit-field) 마이컴(마이크로컨트롤러)

용어집으로 돌아가기