profile image

L o a d i n g . . .

Unit 51. 구조체 멤버 정렬 사용하기

51.1 구조체 크기 알아보기

구조체의 전체 크기는 sizeof 연산자를 사용하면 알 수 있다.

  • sizeof(struct 구조체)
  • sizeof(구조체별칭)
  • sizeof(구조체변수)
  • sizeof 구조체변수

다음은 가상의 네트워크 패킷 구조체 PacketHeader를 정의해서 멤버의 크기와 구조체의 크기를 구하는 코드이다.

#include <stdio.h>

struct PacketHeader {
    char flags;    // 1바이트
    int seq;       // 4바이트
};

int main()
{
    struct PacketHeader header;

    printf("%d\n", sizeof(header.flags));           // 1: char는 1바이트
    printf("%d\n", sizeof(header.seq));             // 4: int는 4바이트
    printf("%d\n", sizeof(header));                 // 8: 구조체 전체 크기는 8바이트
    printf("%d\n", sizeof(struct PacketHeader));    // 8: 구조체 이름으로 크기 구하기

    return 0;
}

실행 결과는 다음과 같다.

1
4
8
8

 

PacketHeader 구조체 안에는 1바이트 크기의 char와 4바이트 크기의 int가 들어있다. 그래서 전체 크기는 5바이트가 나와야 할 것 같은데 실제로는 8바이트가 나왔다.

 

C 언어에서는 구조체를 정렬할 때 멤버 중에서 가장 큰 자료형 크기 배수로 정렬한다. 여기서 가장 큰 자료형은 int이므로 int의 크기 4바이트를 기준으로 정렬한다.

 

구조체 정렬

4바이트로 정렬해서 flags seq가 모두 들어가는 최소 크기는 8바이트이다(4바이트 * 2). 따라서 구조체 정렬을 하면 5바이트가 아닌 8바이트가 된다.

 

그럼 정말 구조체를 정렬한 뒤 멤버의 위치가 그림 51-1처럼 되었는지 확인해보자. 구조체에서 멤버의 위치(offset)를 구할 때는 offsetof 매크로를 사용한다(stddef.h 헤더 파일에 정의되어 있음).

  • offsetof(struct 구조체, 멤버)
  • offsetof(구조체별칭, 멤버)
#include <stdio.h>
#include <stddef.h>   // offsetof 매크로가 정의된 헤더 파일

struct PacketHeader {
    char flags;    // 1바이트
    int seq;       // 4바이트
};

int main()
{
    printf("%d\n", offsetof(struct PacketHeader, flags));    // 0
    printf("%d\n", offsetof(struct PacketHeader, seq));      // 4

    return 0;
}

여기서는 구조체가 4바이트 단위로 정렬하므로 seq의 위치는 1이 아닌 4가 나온다.

 

 

51.2 구조체 정렬 크기 조절하기

각 컴파일러에서 제공하는 특별한 지시자를 사용하면 구조체 정렬 크기를 조절할 수 있다.

  • Visual Studio, GCC 4.0 이상
#pragma pack(push, 정렬크기)
#pragma pack(pop)
  • GCC 4.0 미만
__attribute__((aligned(정렬크기), packed))

 

#include <stdio.h>

#pragma pack(push, 1)    // 1바이트 크기로 정렬
struct PacketHeader {
    char flags;    // 1바이트
    int seq;       // 4바이트
};
#pragma pack(pop)        // 정렬 설정을 이전 상태(기본값)로 되돌림

int main()
{
    struct PacketHeader header;

    printf("%d\n", sizeof(header.flags));    // 1: char는 1바이트
    printf("%d\n", sizeof(header.seq));      // 4: int는 4바이트
    printf("%d\n", sizeof(header));          // 5: 1바이트 단위로 정렬했으므로 
                                             // 구조체 전체 크기는 5바이트

    return 0;
}

위 소스 코드의 실행 결과는 다음과 같다.

1
4
5

pack을 1로 설정하면 1바이트 단위로 정렬하게 되므로 남는 공간 없이 자료형 크기 그대로 메모리에 올라간다.

 

#pragma pack(push, 1)을 한 번 사용하면 그 아래에 오는 모든 구조체에 영향을 주므로 정렬 설정을 한 뒤에는 #pragma pack(pop)를 사용하여 설정을 이전 상태로 되돌린다.

 

-> 구조체를 1바이트 크기로 정렬하는 것은 구조체의 내용을 파일에 쓰거나 네트워크로 전송할 때 꼭 필요하다는 점 알아둬야한다.

 

 

Unit 52. 구조체와 메모리 활용하기

52.1 구조체와 메모리를 간단하게 0으로 설정하기

일일이 멤버에 값을 설정하거나 중괄호를 사용하지 않고, 구조체 변수나 메모리의 내용을 한꺼번에 값을 설정하려면 memset 함수를 사용하면 된다.

  • memset(구조체포인터, 설정할값, sizeof(struct 구조체));
#include <stdio.h>
#include <string.h>    // memset 함수가 선언된 헤더 파일

struct Point2D {
    int x;
    int y;
};

int main()
{
    struct Point2D p1;

    memset(&p1, 0, sizeof(struct Point2D));    // p1을 구조체 크기만큼 0으로 설정

    printf("%d %d\n", p1.x, p1.y);    // 0 0: memset을 사용하여 0으로 설정했으므로
                                      // x, y 모두 0
 
    return 0;
}

memset 함수로 구조체 변수의 값을 설정할 때는 &p1과 같이 주소 연산자 &를 사용하여 변수의 메모리 주소를 구해서 넣어준다. 그리고 설정할 값과 크기를 넣어준다. 여기서는 구조체의 내용을 모두 0으로 설정했고, Point2D 구조체 크기만큼 값을 설정했다.

 

 

52.2 구조체와 메모리 복사하기

memcpy 함수를 사용하면 메모리의 내용을 다른 곳으로 복사할 수 있으며 함수 이름은 memory copy에서 따왔다(string.h 헤더 파일에 선언되어 있습니다).

  • memcpy(목적지포인터, 원본포인터, 크기);
#include <stdio.h>
#include <string.h>    // memcpy 함수가 선언된 헤더 파일

struct Point2D {
    int x;
    int y;
};

int main()
{
    struct Point2D p1;
    struct Point2D p2;

    p1.x = 10;    // p1의 멤버에만 값 저장
    p1.y = 20;    // p1의 멤버에만 값 저장

    memcpy(&p2, &p1, sizeof(struct Point2D));    // Point2D 구조체 크기만큼 p1의 내용을 p2로 복사

    printf("%d %d\n", p2.x, p2.y);    // 10 20: p1의 내용을 p2로 복사했으므로 10 20

    return 0;
}

먼저 구조체 변수 p1, p2를 선언하고 p1의 멤버에만 값을 저장했다.

이제 memcpy 함수를 사용하여 p1의 내용을 p2에 복사한다. 여기서 &p1, &p2와 같이 구조체 변수 앞에 주소 연산자 &를 사용하여 변수의 메모리 주소를 구해서 넣어준다.

 

 

Unit 53. 구조체 배열 사용하기

53.1 구조체 배열 선언하기

구조체 배열은 변수 이름 뒤에 [ ] (대괄호)를 붙인 뒤 크기를 설정한다.

  • struct 구조체이름 변수이름[크기];
#include <stdio.h>

struct Point2D {
    int x;
    int y;
};

int main()
{
    struct Point2D p[3];    // 크기가 3인 구조체 배열 생성

    p[0].x = 10;    // 인덱스로 요소에 접근한 뒤 점으로 멤버에 접근
    p[0].y = 20;
    p[1].x = 30;
    p[1].y = 40;
    p[2].x = 50;
    p[2].y = 60;

    printf("%d %d\n", p[0].x, p[0].y);    // 10 20
    printf("%d %d\n", p[1].x, p[1].y);    // 30 40
    printf("%d %d\n", p[2].x, p[2].y);    // 50 60

    return 0;
}

실행 결과는 다음과 같다.

10 20
30 40
50 60

구조체 배열에서 각 요소에 접근하려면 배열 뒤에 대괄호를 사용하며 대괄호 안에 인덱스를 지정해주면 된다. 이 상태에서 다시 멤버에 접근하려면 . (점)을 사용한다.

복사했습니다!