이 페이지 요약

1. 구조체 멤버 정렬

2. 구조체 배열

3. 구조체 포인터 배열

 

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

구조체가 메모리에 올라갔을 때 멤버를 정렬하는 기능이다.

컴퓨터는 CPU가 메모리에 접근할 때 32비트는 4바이트 단위, 64비트는 8바이트 단위로 접근한다. 만약 32비트 CPU에서 4바이트보다 작은 데이터에 접근하게 되면 내부적으로 시프트 연산이 발생하여 효율이 떨어지기 때문에 C컴파일러에서 CPU가 메모리의 데이터에 효율적으로 접근할 수 있도록 구조체를 일정한 크기로 정렬한다. 구조체 크기가 15나 17바이트가 되면 접근 효율이 떨어지기 떼문에 2, 4, 8, 16바이트 단위로 정렬한다.

구조체의 멤버를 정렬하면 안되는 경우도 있다. 사진파일의 경우 정렬하게 되면 사진을 저장할 때 마다 조금씩 깨질 수 있다. 또한 네트워크로 데이터를 전송할 경우 몇 바이트씩 어떤 순서로 보낼지 규약을 정해놓았는데, 이때 정렬이 발생하면 정해놓은 규약에서 벗어나 받는 쪽에서 데이터를 알아볼 수 없게 된다.

51.1 구조체 크기 알아보기

구조체 전체 크기는 sizeof 연산자를 사용하여 알 수 있다. sizeof 연산자는 다음과 같이 사용할 수 있다.

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

다음은 가상의 네트워크 구조체 PacketHeader를 정의하여 멤버의 크기와 구조체의 크기를 구한 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
struct PacketHeader {
    char flags;
    int seq;
};
 
int main()
{
    struct PacketHeader header;
 
    printf("%d\n"sizeof(header.flags));
    printf("%d\n"sizeof(header.seq));
    printf("%d\n"sizeof(header));
    printf("%d\n"sizeof(struct PacketHeader));
 
    return 0;
}
cs

구조체의 크기를 구하려면 sizeof 연산자 안에 변수나 struct 키워드와 구조체 이름을 넣어주면 된다. PacketHeader 구조체 안에는 1바이트 크기의 char 변수와 4바이트 크기의 int 변수가 들어있어 전체 크기는 5가 나와야 할 것 같지만 8이 나왔다. C에서 구조체들을 정렬할 때는 멤버중에서 가장 큰 자료형 크기의 배수로 정렬한다. 위 구조체에서는 int가 가장 큰 자료형이므로 느끼는 4바이트를 기준으로 정렬하여 구조체 전체 크기는 8바이트가 되었다. 1바이트 크기인 char flags 뒤에는 4바이트를 맞추기 위해 남는 공간에 3바이트가 더 들어가게 되는데 이렇게 구조체를 정렬할 때 남는 공간을 채우는 것을 패딩이라고 한다.

다음 코드를 이용하여 구조체를 정렬한 뒤 멤버의 위치가 위 그림처럼 되는지 확인할 수 있다. 구조체에서 멤버의 위치(offset)를 구할 때는 offsetof 매크로를 사용하여 구할 수 있다. offsetof매크로는 stddef.h 헤더파일에 정의되어 있다.

  • offsetof(struct 구조체, 멤버)
  • offsetof(구조체별칭, 멤버)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <stdio.h>
    #include <stddef.h>
     
    struct PacketHeader {
        char flags;
        int seq;
    };
     
    int main()
    {  
        printf("%d\n", offsetof(struct PacketHeader, flags));
        printf("%d\n", offsetof(struct PacketHeader, seq));
        
        return 0;
    }
    c

offsetof 매크로에 구조체와 멤버를 지정하면 구조체에 해당 멤버의 상대 위치가 반환된다. 첫 멤버의 위치는 0이고 4바이트 단위로 정렬하므로 seq의 위치는 4가 나온다.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
#pragma pack(push, 1)   // 1바이트 크기로 정렬
struct PacketHeader {
    char flags;
    int seq;
};
#pragma pack(pop)       // 정렬 상태를 이전 상태(기본값)로 되돌림
 
int main()
{
    struct PacketHeader header;
 
    printf("%d\n"sizeof(header.flags));
    printf("%d\n"sizeof(header.seq));
    printf("%d\n"sizeof(header));
 
    return 0;
}
cs

구조체를 정의할 때 위 아래로 #pragma pack(push, 1)과 #pragma pack(pop)을 넣었다. pack을 1로 설정하면 1바이트 단위로 정렬하여 남는 공간 없이 자료형 크기 그대로 메모리에 올라간다. #pragma pack(push, 1)을 한 번 사용하면 이후 모든 구조체에 영향을 주므로 정렬 한 뒤 #pragma pack(pop)를 사용하여 설정을 이전 상태로 되돌린다. 만약gcc버전이 4.0미만이면 __attribute__((aligned(1), packed))를 사용한다.
offsetof로 멤버의 위치를 확인하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stddef.h>
 
#pragma pack(push, 1)  
struct PacketHeader {
    char flags;
    int seq;
};
#pragma pack(pop
 
int main()
{
    printf("%d\n", offsetof(struct PacketHeader, flags));
    printf("%d\n", offsetof(struct PacketHeader, seq));
 
    return 0;
}
cs

구조체를 1바이트 단위로 정렬해서 seq의 상대 위치는 1이 나온다. char flags 바로 뒤에 int seq가 오기 때문이다. 구조체의 바이트 크기는 1 이외에도 2, 4, 8, 16등으로 지정할 수 있다.

51.3 퀴즈

정답은 b,d,e 이다.

정답은 e 이다.

정답은 d이다.

정답은 e이다.

51.4 연습문제 : 압축 헤더 크기 구하기

8이 나오려면 구조체의 전체 크기를 구해야 하므로 정답은 sizeof(header)이다.

51.5 연습문제 : 패킷 크기 조절하기

6이 출력되려면 legth의 자료형 크기가 2가 되어야 하므로 정답은 short이다.

51.5 심사문제 : 암호화 헤더 크기 구하기

12가 출력되야 하므로 가장 큰 자료형의 크기는 4가 되도록 하고, 구조체 멤버를 두개 만들면 된다.

int num1;
int num2;

51.7 심사문제 : 패킷 크기 조절하기

3이 출력되야 하므로 구조체 정렬 크기를 자료형에 맞게 조절하고, 1바이트인 char 자료형, 2 바이트인 short 자료형으로 멤버를 하나씩 만들면 된다.

#pragma pack(push,1)
struct Packet{
    char c;
    short s;
};
#pragma pack(pop)

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

구조체도 변수를 선언하거나 메모리를 할당하게 되면 메모리 공간을 차지하므로 메모리 관련 함수를 사용할 수 있다.

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

구조체의 모든 멤버를 0으로 만들기 위해 각 멤버에 접근하여 0을 저장하는 것은 매우 번거로운 작업이다. 따라서 {0, }과 같이 중괄호를 사용하여 모두 0으로 초기화 할 수 있다. 이 방법은 변수에만 사용할 수 있고, malloc 함수로 할당한 메모리에는 사용할 수 없다.

struct 구조체이름 변수이름 = { 0, };

구조체 변수나 메모리의 내용에 한꺼번에 값을 설정하려면 다음과 같이 memset함수를 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D p1;
    
    memset(&p1, 0sizeof(struct Point2D));
    
    printf("%d %d\n", p1.x, p1.y);
    return 0;
}
cs

memset함수로 구조체 변수의 값을 설정할 때는 &p1과 같이 주소연산자 &을 사용하야 메모리 주소를 구해서 넣어줘야 한다. 그리고 설정할 값과 크기를 넣어준다. 위 코드에서는 구조체의 내용을 0으로 설정하고 Point2D구조체 크기만큼 설정했다. 그리고 각 멤버를 출력해보면 모두 설정한 0이 나온다.
다음과 같이 malloc함수로 설정한 동적 메모리에도 값을 설정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D *p1 = malloc(sizeof(struct Point2D));
    
    memset(p1, 0sizeof(struct Point2D));
 
    printf("%d %d\n", p1->x, p1->y);
 
    free(p1);
    return 0;
}
cs

memset 함수로 메모리를 설정할 때 구조체가 포인터 변수이기 때문에 &을 사용하지 않고 그대로 넣어준다. 설정할 값과 크기를 넣어주고 각 멤버를 출력해보면 모두 0이 나온다.

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

내용이 같은 구조체를 만들거나 이미 생성하여 값을 저장한 구조체나 메모리를 다른 곳에 복사할 경우 memcpy 함수를 사용하여 메모리의 내용을 다른 곳에 복사할 수 있다. string.h 헤더파일에 선언되어 있다. memcpy(목적지포인터, 원본포인터, 크기); 의 형태로 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D p1;
    struct Point2D p2;
 
    p1.x = 10;
    p1.y = 20;
 
    memcpy(&p2, &p1, sizeof(struct Point2D));
 
    printf("%d %d\n", p2.x, p2.y);
    return 0;
}
cs

구조체 변수 p1, p2를 선언하고 p1의 멤버에만 값을 저장한 상태에서 memcpy 함수를 사용하여 p1의 내용을 p2에 복사하였다. memcpy함수를 사용할 때는 구조체 변수에 주소 연산자를 사용하여 변수의 메모리 주소를 넣고, 크기는 구조체의 크기를 넣어준다. 목적지 포인터와 원본포인터는 앞쪽이 목적지 포인터이고, 뒷쪽이 원본 포인터이다. p2의 각 멤버를 출력해보면 p1의 멤버에 저장했던 값이 나온다.
다음은 malloc함수로 할당한 동적 메모리끼리 복사하는 방법이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D *p1 = malloc(sizeof(struct Point2D));
    struct Point2D *p2 = malloc(sizeof(struct Point2D));
 
    p1->= 10;
    p1->= 20;
    memcpy(p2, p1, sizeof(struct Point2D));
 
    printf("%d %d\n", p2->x, p2->y);
 
    free(p2);
    free(p1);
    return 0;
}
cs

구조체 포인터 p1, p2를 선언한 뒤 메모리를 할당하여 p1의 멤버에 값을 저장하고 , p2에는 memcpy함수를 사용하여 p1의 내용을 p2에 복사했다. p1과 p2는 메모리를 담고 있는 포인터이므로 주소연산자는 사용하지 않는다.

또한 구조체 변수에서 동적메모리로, 동적메모리에서 구조체 변수로도 내용을 복사할 수 있다.

52.3 퀴즈

정답은 b이다.

정답은 a이다.

52.4 연습문제 : 2차원 좌표 초기화하기

정답은 다음과 같다.

1. #include <string.h>

2. &p, 0, sizeof(struct Point2D)

3. ptr, 0, sizeof(struct Point2D)

52.5 연습문제 : 2차원 좌표 복제하기

p1의 멤버에만 값을 저장했기 때문에 p1의 값을 p2복제해야 하는데 p2는 메모리 주소이므로 정답은 p2, &p1, sizeof(struct Point2D) 이다.

52.6 심사문제 : 인적 정보 삭제하기

출력했을 때 아무 정보도 나오지 않도록 하려면 모든 멤버를 0으로 초기화해야 하므로 정답은 &p1, 0, sizeof(struct Person)이다.

52.7 심사문제 : 인적 정보 복제하기

p2는 구조체 변수이므로 주소 연산자를 사용해서 p1의 값을 복사해와야 한다. 정답은 &p2, p1, sizeof(struct Person) 이다.

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

구조체 변수를 여러개 만들어야 할 경우 구조체 배열을 선언하여 여러개의 구조체 변수를 한 번에 만들 수 있다.

53.1 구조체 배열 선언하기

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D p[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);
    printf("%d %d\n", p[1].x, p[1].y);
    printf("%d %d\n", p[2].x, p[2].y);
    return 0;
}
cs

구조체 변수를 선언할 때 대괄호 안에 크기를 넣어주면 배열로 선언할 수 있다. 배열에서 각 요소에 접근하려면 배열 뒤에 대괄호를 사용하며 대괄호 안에 인덱스를 지정해주면 된다. 이 상태에서 멤버에 접근하려면 점을 사용한다. p[0].x 구조체 배열의 첫 번째 요소의 멤버 x에 접근한다는 뜻이다.
구조체 배열을 선언하는 동시에 초기화하려면 다음과 같이 중괄호 안에 중괄호를 사용하면 된다.

  • struct구조체이름 변수이름[크기] = { { .멤버이름1 = 값1, .멤버이름2 = 값2 }, { .멤버이름1 = 값3, .멤버이름2 = 값4 } };
  • struct구조체이름 변수이름[크기] = { { 값1, 값2 }, { 값3, 값4 } };

모든 요소의 멤버를 0으로 초기화 하여면 구조체 배열을 선언하며 {0, }을 할당하면 된다.

53.2 구조체 포인터 배열 선언하기

구조체 포인터 배열을 만들고 malloc함수로 각 요소에 메모리를 할당하면 요소마다 메모리를 할당할 수 있다. 구조체 포인터 배열은 포인터 변수 이름 뒤에 대괄호를 붙인 뒤 크기를 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdlib.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D *p[3];
 
    for(int i  = 0; i < sizeof(p) / sizeof(struct Point2d *); i++)
    {
        p[i] = malloc(sizeof(struct Point2D));
    }
 
    p[0]->= 10;
    p[0]->= 20;
    p[1]->= 30;
    p[1]->= 40;
    p[2]->= 50;
    p[2]->= 60;
 
    printf("%d %d\n", p[0]->x, p[0]->y);
    printf("%d %d\n", p[1]->x, p[1]->y);
    printf("%d %d\n", p[2]->x, p[2]->y);
 
    for(int i  = 0; i < sizeof(p) / sizeof(struct Point2d *); i++)
    {
        free(p[i]);
    }
    return 0;
}
cs

구조체 포인터를 선언하고 사용하려면 메모리를 할당해야 한다. 배열의 요소 개수 만큼 반복하면서 각 요소에 구조체 크기 만큼 메모리를 할당해야 한다. 구조체 포인터 배열에는 포인터가 들어있으므로 요소 개수를 구하려면 구조체 포인터 배열의 전체 크기에서 구조체 포인터의 크기로 나눠주면 된다. sizeof(struct Point2D *)는 구조체 포인터의 크기이다. 멤버에 접근할 때는 배열안에 들어있는 요소가 포인터이므로 화살표 연산자를 사용하여 멤버에 접근해야 한다. 구조체 배열의 사용이 끝나면 배열 크기만큼 반복하며 각 요소에 할당된 동적 메모리를 해제해야 한다.

53.3 퀴즈

정답은 b이다.

정답은 b이다.

정답은 d이다.

정답은 f이다.

53.4 연습문제 : 2차원 좌표 출력하기

정답은 다음 코드와 같다.

// 1
struct Point2D p[3];

// 2
for(int i = 0; i < sizeof(p) / sizeof(struct Point2D); i++)
{
	printf("%d %d\n", p[i].x, p[i].y);
}

구조체 배열의 모든 요소를 출력할 때 0번 인덱스 부터 마지막 인덱스까지 for 문을 이용하여  반복하여 출력할 수 있다.

 

53.5 연습문제 : 인적 정보를 초기화하기

정답은 다음 코드와 같다.

// 1.
struct Person *p[3000];

// 2.
for(int i = 0; i < sizeof(p) / sizeof(struct Person *); i++)
{
	p[i] = malloc(sizeof(struct Person));
    memset(p[i], 0, sizeof(struct Person));
}

구조체 포인터 배열이므로 첫번째 인덱스 부터 마지막 인덱스까지 메모리를 할당해주고, 메모리를 0으로 초기화 해야한다.

 

 53.6 심사문제 : 선의 길이 구하기

정답은 다음 코드와 같다.

for(int i=0; i<(sizeof(p) / sizeof(struct Point2D))-1; i++)
{
    double len_x = p[i+1].x - p[i].x;
    double len_y = p[i+1].y - p[i].y;
    length += sqrt((len_x)*(len_x) + (len_y)*(len_y));
}

두 점 사이의 거리를 구하는 공식을 이용해서 거리를 구했고, math.h 헤더의 sqrt 함수를 이용하여 제곱근을 구했다.

 

53.7 심사문제 : 나이가 가장 많은 사람 찾기

정답은 다음 코드와 같다.

int big_age = 0;

for (int i = 0; i < sizeof(p) / sizeof(struct Person *); i++)
{
    p[i] = malloc(sizeof(struct Person));
    scanf("%s %d", p[i]->name, &p[i]->age);
    if(p[i]->age >= p[big_age]->age)
    {
        big_age = i;
    }
}

printf("%s\n", p[big_age]->name);

가장 큰 나이의 인덱스를 저장할 변수를 만들고, 배열 길이만큼 반복하면서 메모리를 할당하고, 할당한 메모리에 값을 입력받는다. 입력받은 나이가 저장되어있던 인덱스의 나이보다 크면 현재 인덱스를 저장한다. 반복문이 끝나면 저장된 인덱스에 해당한는 이름을 출력한다.

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C] FTZ (1 ~ 7(문제오류))  (0) 2021.03.24
[Project H4C] C언어 코딩도장(14)  (0) 2021.03.23
[Project H4C] PLT, GOT  (0) 2021.03.21
[Project H4C] SFP, RET  (0) 2021.03.21
[Project H4C] Buffer Overflow  (0) 2021.03.21

+ Recent posts