스터디 그룹/ProjectH4C

ProjectH4C 3개월 2주차 과제 (UNIT 51, 52, 53)

📖UNIT 51.


📒 구조체 크기 알아보기

구조체의 크기를 알아보자. 어떻게 계산하면 될까?

sizeof 함수를 사용하도록 하자.

 

우선 가상의 네트워크 헤드를 구조체로 만들었다.

#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;
}

 

PacketHeader 구조체 내에는 flags와 seq가 존재한다. 각각 char, int 로 선언되었기에 1바이트 4바이트를 차지한다.

 

그럼 당연히 header나 struct PacketHeader의 크키는 5 바이트가 나와주어야 한다.

 

하지만 8바이트가 되어있다................ ??

 

그 이유는 구조체를 정렬할 때 가장 큰 자료형의 크기의 배수로 정렬하기 때문이다.

즉, 여기서 가장 큰 자료형은 4바이트이다. 딱 필요한 바이트는 5바이트지만, 8바이트가 사용되는 것이다.

 

그래서 남은 3바이트는 패딩을 하게 된다.

그래서 이런 모습을 띄게 된다. 그럼 저 공간은 무슨 값으로 채워질까 ?


📒 구조체 정렬 크기 조정하기

뭐 경우에 따라서 구조체 정렬을 피해야 할 수도 있다.

그런 경우에는 어떻게 하면 될까?

 

우선 한가지 알아야 할 것이, 정렬되는 사이즈를 정하지 않는다면, 자동으로 가장 큰 자료형의 배수로 정렬된다는 것이다.

만약 그 정령되는 단위가 1이라면 어떻게 될까.

 

당연히 패딩되는 공간은 생기지 않을 것이다.

 

#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;
}

 

이렇게 header의 사이즈가 5바이트로 결정된다.

 

만약 단위가 3이라면 어떻게 될까.

이렇게 1바이트 만큼 패딩을 해주는 것을 알 수 있다.

 


📖UNIT 52.


📒 구조체와 메모리를 0으로 설정하기

먼저 구조체와 메모리를 간단하게 0으로 설정해보자.

구조체도 결국 변수처럼 사용하게 된다면, 메모리를 사용하기에 메모리 관련 함수들을 사용할 수 있다.

 

구조체 변수나 메모리의 값을 한 번에 설정하고 싶다면 memset 함수를 사용하면 된다.

 

#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;
}

이런 실행결과를 얻을 수 있다.

 

결국 둘 다 0으로 초기화 된 것이다.

 

이런 메모리 구조를 띄고 있는 상황이다.

memset함수를 사용할 때는 첫 번째 인자에 무조건 주소를 넣어주어야 한다.

 


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

이번엔 구조체 변수를 p1, p2를 선언하고 p1의 값들을 p2로 옮겨보도록 하자.

옮길 때는 memcpy 함수를 사용하면 된다. 말 그대로 memory copy 이다.

 

#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;
}

이렇게 해주면 된다.

 

동적으로 할당된 구조체여도 마찬가지이다. 하지만 조심할 것은,

#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일
#include <string.h>    // memcpy 함수가 선언된 헤더 파일

struct Point2D {
    int x;
    int y;
};

int main()
{
    struct Point2D *p1 = malloc(sizeof(struct Point2D));
    struct Point2D *p2 = malloc(sizeof(struct Point2D));

    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

    free(p2);
    free(p1);

    return 0;
}

p1.x, p2.x 로 쓰는 것이 아니라 p1->x p2->x로 써야 한다는 것이다.

 


📖UNIT 53.

구조체로도 배열을 만들 수 있을까 ?

 

당연히 만들 수 있다.

 

#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;
}

이렇게 코드를 짰을 때 생성되는 메모리를 확인해보자.

#include <stdio.h>

struct Point2D {
    int x;
    int y;
};

int main()
{
    // 구조체 배열을 선언하면서 초기화
    struct Point2D p1[3] = { { .x = 10, .y = 20 }, { .x = 30, .y = 40 }, { .x = 50, .y = 60 } };

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

    // 구조체 배열을 선언하면서 초기화
    struct Point2D p2[3] = { { 10, 20 }, { 30, 40 }, { 50, 60 } };

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

    return 0;
}

이렇게 구조체 배열을 선언과 동시에 초기화 해주는 방법도 있으니 참고하도록 하자.

이 상황에서 p1과 p2의 크기는 몇이 나올까.

 

사이즈는 당연히 24바이트가 나와준다.


📒 구조체 포인터 배열 선언

이번엔 구조체 포인터 배열을 선언하도록 해보자.

항상 조심할 점은, 구조체 포인터 배열은 -> 연산자를 사용한다는 것이다.

 

#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일

struct Point2D {
    int x;
    int y;
};

int main()
{
    struct Point2D *p[3];    // 크기가 3인 구조체 포인터 배열 선언

    // 구조체 포인터 배열 전체 크기에서 요소(구조체 포인터)의 크기로 나눠서 요소 개수를 구함
    for (int i = 0; i < sizeof(p) / sizeof(struct Point2D *); i++)    // 요소 개수만큼 반복
    {
        p[i] = malloc(sizeof(struct Point2D));    // 각 요소에 구조체 크기만큼 메모리 할당
    }

    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

    for (int i = 0; i < sizeof(p) / sizeof(struct Point2D *); i++)    // 요소 개수만큼 반복
    {
        free(p[i]);    // 각 요소의 동적 메모리 해제
    }

    return 0;
}

그래서 이렇게 코딩을 해볼 수 있겠다.

 

간단하게 내가 이해한 바로는, 먼저 크기가 3인 구조체 포인터 배열을 선언해야 한다.

이부분에 대한 이해는 구조체 포인터를 좀 더 완벽히 이해하고 다시 진행해야 할 것 같다.