Unit 51 구조체 멤버 정렬 사용하기
-구조체 멤버 정렬-
cpu가 메모리에 접근할 때 32bit는 4바이트 단위, 64bit는 8바이트 단위로 접근한다. 만약 32bit에서 cpu가 4바이트보다 작은 데이터에 접근한다면 실질적으로 더 작은 바이트만 사용하는 것이지만 사용은 4바이트만큼 해 비효율적이다. 따라서 c언어 컴파일러는 cpu가 메모리의 데이터에 효율적으로 접근할 수 있도록 구조체를 일정한 크기로 정렬을 한다고 한다.
무조건 정렬이 좋은 것은 아니다. 사진 같은 파일을 저장한다 가정할 때, 이런식으로 정렬이 일어난다면 사진이 깨져버릴 수 있다. 이런 경우 정렬을 사용하면 안된다.
#include <stdio.h>
typedef struct stru {
char x; // 1byte
int y; // 4byte
} A; // 도합 5byte
int main()
{
A a; // 구조체 변수 선언
printf("%d\n", sizeof(a.x)); // x의 크기 확인
printf("%d\n", sizeof(a.y)); // y의 크기 확인
printf("%d\n", sizeof(a)); // 구조체 변수로 전체 크기 확인
printf("%d\n", sizeof(struct stru)); // 구조체 이름으로 전체 크기 확인
return 0;
}
위는 구조체를 만든 후 그 구조체 변수들의 크기와, 전체 크기를 출력해주는 코드다. 1byte(char) + 4byte(int)해서 도합 5byte가 나올 것 같지만 결과는 다르다.
첫 줄, 두번째 줄은 예상하던 값인 1, 4가 나왔지만 3, 4번째 줄은 다르다.
원래대로라면 5byte가 나오는 것이 맞지만, 앞서 정리한 c언어 컴파일러가 효율적으로 데이터에 접근할 수 있도록 정렬을 해줬기 때문에 8이 나오게 된 것이다.
c언어에서 구조체를 정렬할 때는 멤버 변수 중에서 가장 큰 자료형 크기의 배수로 정렬한다.
위 코드에서 가장 큰 자료형은 int이므로 int의 배수인 8이 된 것이다.
위는 c언어 코딩도장에 나왔있는 정렬의 예시 사진이다.
원래는 맨 위 사진처럼 1byte, 4byte로 선언됐지만 컴파일 과정을 거치면서 1byte에 패딩을 추가해서 4byte로 만들어 버려, 최종적으로 전체 크기를 8byte로 만드는 것이다.
-offsetof
offsetof는 stddef.h에 정의된 메크로로 구조체에서 멤버의 offset을 구해주는 역할을 한다.
사용은 offsetof(<struct 구조체>, <멤버>) 또는 별칭을 써서 offsetof(<구조체>, <멤버>)로 하면 된다.
#include <stdio.h>
#include <stddef.h>
typedef struct stru {
char x; // 1byte
int y; // 4byte
} A; // 도합 5byte
int main()
{
A a; // 구조체 변수 선언
printf("%d\n", offsetof(A, x)); // offset 출력
printf("%d\n", offsetof(A, y)); // offset 출력
return 0;
}
offsetof에 typedef로 만든 별칭을 넣어서 x, y 멤버를 각각 출력해봤다.
실행해보면 y의 offset이 1이 아닌 4가 나오는 것을 볼 수 있다. 왜냐하면 x 다음에 3byte 크기의 null padding 영역이 존재하기 때문에 y의 영역은 4부터 시작하는 것이다.
-#pragma pack
이 구조체 정렬을 안하는 방법은 간단하다.
#pragma pack(push, <정렬 크기>)
#pragma pack(pop)
을 사용하면된다.
push의 경우는 정렬 크기만큼 데이터를 정렬하게 해준다.
pop은 push와 <정렬 크기>를 사용하면 아래에 오는 모든 구조체에 영향을 주기 때문에 정렬 설정을 한 뒤, 설정을 이전 상태로 되돌려주는 역할을 한다.
#include <stdio.h>
#include <stddef.h>
#pragma pack(push, 1) // 1byte 크기로 정렬
typedef struct stru {
char x; // 1byte
int y; // 4byte
} A; // 도합 5byte
#pragma pack(pop) // 설정 되돌림
int main()
{
A a; // 구조체 변수 선언
printf("%d\n", offsetof(A, x));
printf("%d\n", offsetof(A, y));
return 0;
}
위는 pack으로 1byte 크기만큼 구조체를 임의로 정렬한 모습이다. 1byte 크기로 정렬하도록 했으니 padding 없이 5byte 크기 그대로 메모리에 올라갈 것이다.
위 사진처럼 y 멤버의 offset 위치가 4가 아닌 1로 된 것을 볼 수 있다.
Unit 51 구조체와 메모리 활용하기
-구조체와 메모리 활용-
-memset
구조체의 멤버를 모두 0으로 만들려면 일일이 각 멤버를 지정해서 0을 넣어줘야했다.
struct <구조체이름> <변수이름> = { 0, }; // 구조체 변수의 내용을 모두 0으로 초기화
위 방법을 사용하면 가능하긴 하지만 malloc으로 동적으로 할당된 메모리에는 사용할 수가 없었다.
하지만 memset 함수를 사용한다면 구조체 변수나 메모리의 내용을 한꺼번에 값을 설정할 수 있다. 이 memset 함수는 string.h에 선언돼 있다.
사용은 memset(<구조체 포인터>, <설정 값>, sizeof(<구조체>));로 하면 된다.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct stru
{
int a;
int b;
} A;
// 구조체 선언 및 별칭 설정
int main()
{
A *a = malloc(sizeof(A)); // 구조체 메모리 동적 할당
memset(a, 0, sizeof(A)); // memset 함수로 구조체 변수 멤버들의 값을 0으로 변경
printf("%d %d", a->a, a->b); // 구조체 변수 멤버들 출력
free(a);
}
위는 구조체 포인터를 선언해 메모리를 동적 할당한 후 memset 함수로 멤버들의 값을 0으로 설정한 코드다.
결과를 보면 멤버들이 0으로 설정됐음을 알 수 있다.
-memcpy
memcpy는 구조체를 다른 곳에 복사해주는 역할을 해준다. 이 역시 string.h에 선언돼 있다.
사용은 memcpy(<목적지 포인터>, <원본 포인터>, <크기>);로 해주면 된다.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct stru
{
int a;
int b;
} A; // 구조체 선언
int main()
{
A a;
A b;
// 구조체 변수 선언
a.a = 1;
a.b = 2;
// 구조체 변수 a의 맴버들에 값 설정
memcpy(&b, &a, sizeof(A)); // 구조체 맴버 a를 구조체 맴버 b로 복사
printf("%d %d", b.a, b.b); // b의 멤버들 출력
}
위는 memcpy를 이용해 a 구조체를 b에 복사하는 코드다.
결과를 보면 a의 멤버들에 저장했던 값이 구조체 변수 b의 멤버들에 복사됐음을 알 수 있다.
Unit 53 구조체 배열 사용하기
-구조체 배열-
구조체 배열은 struct <구조체 명> <변수 명>[<크기>]로 선언할 수 있다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
typedef struct stru
{
int x;
int y;
} A; // 구조체 선언 및 별칭 설정
int main()
{
A a[2]; // 2 크기의 구조체 배열 선언
a[0].x = 1;
a[0].y = 2;
a[1].x = 3;
a[1].y = 4;
// 구조체 멤버들에 값 저장
printf("%d %d\n", a[0].x, a[0].y);
printf("%d %d", a[1].x, a[1].y);
// 구조체 멤버들 출력
}
위 코드처럼 구조체 배열을 선언하고 멤버에 접근할 수 있다. 접근 시 [<인덱스>]. 을 사용해주면 된다.
결과를 보면 각 인덱스의 구조체 변수 멤버들이 잘 출력되는 것을 볼 수 있다.
A a[2] = { {.x = 1, .y = 2 }, {.x = 3, .y = 4}};
A b[2] = {0, } // 모든 멤버들을 0으로 초기화
위처럼 구조체 배열을 선언하는 동시에 초기화할 수도 있다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
typedef struct stru
{
int x;
int y;
} A; // 구조체 선언 및 별칭 설정
int main()
{
A *a[2]; // 구조체 배열 포인터 선언
for (int i = 0; i < sizeof(a) / sizeof(A *); i++) // 요소 개수만큼 반복
{
a[i] = malloc(sizeof(A));
} // 메모리 동적 할당
a[0]->x = 1;
a[0]->y = 2;
a[1]->x = 3;
a[1]->y = 4;
// 각 인덱스에 해당하는 변수의 멤버에 값 저장
printf("%d %d\n", a[0]->x, a[0]->y);
printf("%d %d", a[1]->x, a[1]->y);
// 출력
for (int i = 0; i < sizeof(a) / sizeof(A *); i++) // 요소 개수만큼 반복
{
free(a[i]);
}
// 메모리 해제
}
배열 포인터 선언 및 메모리 할당하는 방법과 똑같다. 구조체 배열 포인터 선언 및 메모리 할당하는 방법도 비슷하게 for문으로 메모리를 할당하고 for문으로 메모리를 해제해주면 된다. 단 멤버 접근은 .이 아닌 ->로 해줘야한다.
동적할당의 원리는 이미 배열 포인터 부분에서 다뤘으므로 넘어가겠다.
멤버들이 잘 출력되는 것을 볼 수 있다.
Unit 51 ~ Unit 53 문제
구조체의 크기는 sizeof(<별칭 혹은 struct 구조체 명>)를 통해 구할 수 있다.
답 : b, d, e(괄호 없이도 가능했다..)
위에서 정리하진 않았지만 gcc 4.0 미만에서 구조체를 1바이트로 정렬하려면 __attribute__((aligned(<정렬 크기>, packed))를 써주면 된다.
답 : e
구조체를 정렬할 때 기준이 되는 자료형은 크기가 가장 큰 자료형이다.
답 : d
구조체 정렬 크기를 조절하는 방법은
#pragma pack(push, <크기>)
#pragma pack(pop)
으로 해주면 된다.
답 : e
8이 출력되는 것이 목적인데, 주어진 구조체의 크기는 5byte다. 하지만 c언어 컴파일러에 의해 가장 큰 자료형을 기준으로 정렬이 되므로 padding이 들어가 총 크기가 8byte이 될 것이다. 따라서 빈칸에 sizeof(header);를 넣어주면 될 것 같다.
#include <stdio.h>
struct CompressHeader
{
char flags;
int version;
};
int main()
{
struct CompressHeader header;
printf("%d\n", sizeof(header)); // 구조체 변수의 크기를 구해서 출력
return 0;
}
구조체의 자료형을 건들여서 6이 출력되도록 만드는 것이 목적이다. 1byte 크기로 정렬되므로 멤버들의 자료형 크기가 도합 6이 되도록하면 될 것 같다.
#include <stdio.h>
#pragma pack(push, 1)
struct Packet {
short length; // 2byte 크기의 자료형인 short 사용
int seq;
};
#pragma pack(pop)
int main()
{
struct Packet pkt;
printf("%d\n", sizeof(pkt));
return 0;
}
구조체의 크기가 12가 되도록 만드는 것이 목적이다. 따로 정해진 정렬 크기가 없으므로 가장 큰 자료형을 기준으로 정렬된다는 점을 고려해야한다.
#include <stdio.h>
struct EncryptionHeader {
char flags;
int a; // 4byte
int b; // 4byte
}; // 정렬에 의해 총 크기 12byte
int main()
{
struct EncryptionHeader header;
printf("%d\n", sizeof(header));
return 0;
}
int 변수를 두 번 써줘서 8byte를 확보했고, 이 int를 기준으로 정렬되기 때문에 char형 변수 역시 크기가 4byte가 되서 총 12byte가 된다.
이번에는 구조체를 직접 선언해서 구조체의 크기가 3이 되도록 하는 것이 목적이다.
이번에는 앞서 배운 정렬 크기를 조정해서 3으로 만들어 보겠다.
#include <stdio.h>
#pragma pack(push, 1) // 정렬 크기를 1로 설정
struct Packet
{
char x; // 1byte
short y; // 2byte
}; // 정렬 크기가 1이므로 도합 3byte
#pragma pack(pop)
int main()
{
struct Packet pkt;
printf("%d\n", sizeof(pkt));
return 0;
}
정렬 크기를 1로 설정하고 char, short형 변수를 각각 선언해서 도합 3byte가 되도록 작성했다.
구조체 변수의 멤버들을 모두 0으로 초기화하는 방법은 memset(<포인터>, 0, <크기>);로 해주면 된다.
답 : b
구조체를 복사하는 방법은 memcpy(<목적지 포인터>, <원본 포인터>, <크기>)로 해주면 된다. 위의 경우는 p1은 포인터이므로 <원본 포인터> 위치에 그대로 써주고, p2는 포인터가 아니므로 &을 붙여서 <목적지 포인터> 위치에 써주면 된다. <크기>에는 Point2D의 크기를 sizeof로 구해서 작성하면 된다. memcpy(&p2, p1, sizeof(struct Point2D));
답 : a
memset 함수를 이용해서 구조체 변수 p, *ptr의 멤버들을 0으로 초기화 시키는 것이 목적이다. 1번 칸에는 memset을 사용하기 위해 string.h를 include하고 2, 3에는 <포인터>, <초기화할 값>, <크기>를 써주면 된다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // memset 함수를 사용하기 위해 string.h include
struct Point2D {
int x;
int y;
};
int main()
{
struct Point2D p;
struct Point2D *ptr = malloc(sizeof(struct Point2D));
memset(&p, 0, sizeof(struct Point2D)); // p의 멤버들을 0으로 초기화
memset(ptr, 0, sizeof(struct Point2D)); // ptr의 멤버들을 0으로 초기화
printf("%d %d %d %d\n", p.x, p.y, ptr->x, ptr->y);
free(ptr);
return 0;
}
p1의 멤버들을 p2로 복사하는 것이 목적인 문제다. 주어진 칸인 memcpy의 인자들을 각각 <목적지 포인터> <원본 포인터> <크기>로 채워주면 될 것 같다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Point2D {
int x;
int y;
};
int main()
{
struct Point2D p1;
struct Point2D *p2 = malloc(sizeof(struct Point2D));
p1.x = 10;
p1.y = 20;
memcpy(p2, &p1, sizeof(struct Point2D)); // p1의 멤버들의 값을 p2로 복사
printf("%d %d\n", p2->x, p2->y);
free(p2);
return 0;
}
memset안의 인수를 채워서 아무것도 출력하지 않는 것이 목적이다. p1의 모든 멤버들을 NULL로 채워주면 될 것 같다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
struct Person {
char name[20];
int age;
char address[100];
};
int main()
{
struct Person p1;
strcpy(p1.name, "홍길동");
p1.age = 30;
strcpy(p1.address, "서울시 용산구 한남동");
memset(&p1, NULL, sizeof(struct Person)); // p1의 모든 멤버에 NULL을 저장
printf("이름: %s\n", p1.name);
printf("나이: %d\n", p1.age);
printf("주소: %s\n", p1.address);
return 0;
}
memset 함수에 적절한 인수를 넣어서 p1의 모든 멤버들에 NULL이 들어가도록 작성했다.
memcpy의 인수를 채워서 p1 멤버들의 값을 p2로 복사하는 것이 목적이다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Person {
char name[20];
int age;
char address[100];
};
int main()
{
struct Person *p1 = malloc(sizeof(struct Person));
struct Person p2;
strcpy(p1->name, "고길동");
p1->age = 40;
strcpy(p1->address, "서울시 서초구 반포동");
memcpy(&p2, p1, sizeof(struct Person)); // memcpy 함수로 p1 멤버들의 값을 p2로 복사
printf("이름: %s\n", p2.name);
printf("나이: %d\n", p2.age);
printf("주소: %s\n", p2.address);
free(p1);
return 0;
}
&p2(p2는 일반 구조체 변수이므로), p1(포인터이므로), sizeof(struct Person)을 memcpy안에 넣어줘서 풀었다.
구조체 배열은 <struct 구조체 명 or 구조체 별칭> <변수 명>[<크기>]로 만들 수 있다.
답 : b
구조체 포인터 배열을 선언하는 방법은 <struct 구조체 명 or 별칭> *<변수 명>[<크기>]로 만들 수 있다.
답 : b
구조체 배열에서 멤버에 접근하는 방법은 <변수 명>[<인덱스>].<멤버 명>으로 접근하면 된다.
답 : d(c의 경우 인덱스 범위를 벗어났다.)
구조체 포인터 배열에서 멤버에 접근하는 방법은 구조체 배열과 비슷하지만 . 대신 ->를 써줘야한다.
답 : f(b는 인덱스 범위를 벗어났다.)
구조체 배열을 선언, 구조체 배열의 멤버들을 출력하는 것이 목적이다. 출력은 for문으로 해주면 편할 것 같다.
#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;
for (int i = 0; i < 3; i++) // 3번 반복
{
printf("%d %d\n", p[i].x, p[i].y); // 해당 인덱스의 멤버 출력 후 개행
}
return 0;
}
3000 크기의 구조체 배열을 선언한 후 모두 0으로 초기화 시키는 것이 목적이다. free 함수가 존재하는 것을 봐서 포인터 배열로 만든 후 malloc을 통해 동적 할당을 해줘야할 것 같다. 또한 for문을 돌며 메모리를 할당함과 동시에 memset으로 해당 인덱스의 멤버들을 0으로 초기화 해주면 될 것 같다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Person {
char name[20];
int age;
char address[100];
};
int main()
{
struct Person *p[3000]; // 3000 크기의 구조체 포인터 배열 선언
for (int i = 0; i < 3000; i++) // 3000번 반복
{
p[i] = malloc(sizeof(struct Person));
memset(p[i], 0, sizeof(struct Person));
} // 메모리 동적할당 및 해당 인덱스의 멤버들을 memset으로 0 초기화
printf("%d\n", p[2000]->age);
for (int i = 0; i < sizeof(p) / sizeof(struct Person *); i++)
{
free(p[i]);
}
return 0;
}
입력된 4개의 좌표를 구조체 배열에 저장하고 첫 번째 좌표부터 마지막 좌표까지의 길이를 구해 출력하는 것이 목적이다. 입력 부분은 구현돼 있으니 두 점 사이의 거리를 구하기에서 배운 피타고라스 공식을 이용해 for문으로 반복하며 길이를 누적해주면 될 것 같다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
struct Point2D {
int x;
int y;
};
int main()
{
struct Point2D p[4];
double length = 0.0f;
scanf("%d %d %d %d %d %d %d %d",
&p[0].x, &p[0].y, &p[1].x, &p[1].y, &p[2].x, &p[2].y, &p[3].x, &p[3].y
);
for (int i = 0; i < sizeof(p) / sizeof(struct Point2D *); i++) // 배열 크기만큼 반복
{
if (i == 3) // 3일 경우 반복 종료
break;
int a = pow((p[i + 1].x - p[i].x),2);
int b = pow((p[i + 1].y - p[i].y),2);
length += sqrt(a + b); // 길이를 구해서 length에 누적
// 두 점 사이의 길이 구하기(피타고라스 공식)
}
printf("%f\n", length);
return 0;
}
배열 크기만큼 반복하며 i번째 인덱스의 x, y와 i+1번째 인덱스의 x, y의 차의 제곱의 제곱근을 구해서 length에 누적시켜줬다. 만약 i가 3일 경우에는 더 이상 구할 길이가 없으므로 반복을 종료한다.
이름, 나이가 5번 입력되는데 입력된 사람중 나이가 가장 많은 사람의 이름을 출력되게 만드는 것이 목적이다. 일단 포인터 배열로 선언됐기 때문에 동적 할당 후 scanf로 입력을 받으면 될 것 같다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
struct Person {
char name[30];
int age;
};
int main()
{
struct Person *p[5];
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); // 입력
}
int maxIndex = 0; // 가장 나이가 많은 사람의 인덱스
for (int i = 0; i < sizeof(p) / sizeof(struct Person *); i++) // 배열 크기만큼 반복
{
if (p[maxIndex]->age < p[i]->age)
maxIndex = i;
// maxIndex와 i 인덱스의 age 값을 비교하고 i 인덱스의 age가 더 크다면 maxIndex를 i로 변경
}
printf("%s", p[maxIndex]->name); // maxIndex 인덱스의 name 출력
for (int i = 0; i < sizeof(p) / sizeof(struct Person *); i++)
{
free(p[i]);
}
return 0;
}
for문으로 배열 크기만큼 반복하며 메모리 동적할당을 함과 동시에 scanf로 입력을 받았다. 그 후 for문을 한 번 더 돌려서 maxIndex와 i 인덱스의 age 값을 비교해 i 인덱스가 더 크다면 maxIndex를 i로 변경하는 식으로 나이가 가장 많은 사람을 구한 후 반복이 끝난 후 maxIndex 인덱스의 name을 출력해줬다.
'Old (2021.01 ~ 2021.12) > Programming' 카테고리의 다른 글
c언어 코딩도장 Unit 56 ~ Unit 57 (0) | 2021.04.11 |
---|---|
c언어 코딩도장 Unit 54 ~ Unit 55 (0) | 2021.03.27 |
c언어 코딩도장 Unit 48 ~ Unit 50 (0) | 2021.03.13 |
c언어 코딩도장 Unit 45 ~ Unit 47 (0) | 2021.03.13 |
c언어 코딩도장 Unit 42 ~ Unit 44 (0) | 2021.03.13 |