오늘은 구조체(Structure)에 대해서 공부해 보았다. 구조체는 사용자 정의 자료형으로서 다음과 같은 코드로 정의가 가능하다
struct 구조체명
{
자료형 변수명;
...
};
비쥬얼스튜디오로 한번 만들어보자

이렇게 작성해주면 Date란 이름의 구조체를 정의할 수 있고 그 구성요소는 year, month, day가 있다. 앞서 말한 대로 구조체는 사용자 정의 자료형이다. 즉 여태껏 변수들의 자료형으로 사용해 온 int, float와 같은 자료형의 포지션이라고 생각해 주면 된다. 그러면 다음과 같이 사용한다고 생각할 수 있다.

Date라는 자료형의 변수 Brithday를 선언해준 것이다. 이는 평소에 사용하던 변수 선언처럼 자료형 변수명; 형식으로 선언을 하지만 구조체는 변수의 선언과 조금 다른 점이 있다. 바로 struct라는 접두사를 붙여서 이가 구조체라는 것을 명시해 주어야 한다는 것이다. 이 차이점은 꼭 기억하고 넘어가도록 하자 그럼 이제 구조체 안의 내용물에 각각의 값을 대입해주어야 한다. 이때 사용하는 것이 구조체 멤버 접근 연산자 '.'이다.

Date라는 구조체의 자료형 변수 Birthday라고 우리는 이미 선언해 두었다 여기서 Year에 값을 넣기 위해서는 위와 같이 Birthday.Year로 구조체 멤버 연산자를 사용해서 작성한 후 그 수를 대입해주면 된다. 그럼 값이 올바르게 출력되는지 보도록 하자.

이런식으로 정의한 구조체는 다른 기본 자료형과 같이 스택메모리에 저장된다. 즉, 초기화하지 않는다면 0이 아닌 다른 쓰레기 값이 저장되어 있다.

그렇기에 변수선언할 때 처럼 선언 때 초기화 해주는 걸 습관화하도록 하자 앞에서 구조체를 사용할 때 앞에 struct를 기입해서 이것이 구조체라는 것을 명시해야 할 필요가 있다고 했다. 사실 구조체도 자료형처럼 struct 키워드를 빼고 자료형만 적을 수 있고 이게 훨씬 편리하다. 이때 사용하는 것이 typedef, type definition의 약자로서 이미 정의되어 있는 자료형에 새로운 별명을 지어준다.

사실 익숙하게 봤던 size_t도 typedef unsighned int size t로 _t가 접미사로 붙으면 typedef 되었다고 생각하면 된다.


과연 저 struct Date Birthday2; 가 컴파일이 가능할까? 정답은 안된다 왜냐면 우리는 Date라는 스트럭처를 선언하지 않았기 때문! Date_t Birthday2; 라 선언해야 될 것이다.

구조체도 자료형이기 때문에 포인터도 당연히 사용이 가능하다. 다음 예제로 확인해보자




다만 주의사항 줄을 잘 보도록 하자 ++연산자보다. 연산자가 더 우선순위에 있기에 반드시 둘을 사용할 경우 괄호를 쳐주는 것이 좋다. '.' 말고도 구조체 멤버 접근 연산자로는 '->'가 있다. 구조체 포인터에 역참조 연산자와 '.'연산자를 합친 연산자로서 우선순위가 1순위인 연산자이기 때문에 위의 상황처럼 괄호를 칠 필요가 없다.


구조체를 함수 반환 자료형으로 사용한다면 실질적으로 여러 개의 값이 반환 가능하다.


같은 구조체 자료형 끼리 대입 연산도 가능하다고 한다. 컴파일러마다 다르지만 각 멤버 변수를 돌면서 하나씩 대입되거나 메모리를 통째로 복사한다.


다음 예제를 보고 문제점을 찾아보자


해당 코드에서는 복사한 클론의 이름의 값을 바꿨는데 원본의 이름도 바뀌어 버린 것이다. 이런 것을 얕은 복사라고 한다. 예컨대
int a = 10;
int* ptr1 = &a;
int* ptr2 = ptr1;
이렇게 작성을 한다면 ptr2와 ptr1은 동일한 a의 주소를 갖게 된다. 이 상황에서
*ptr2 = 7;
이렇게 작성하게 되면 원본인 a값도 7로 바뀌게 된다 이를 얕은 복사라고 한다. 이를 피하기 위해서는 다음과 같이 한다.
int a = 10;
int* ptr1 = &a;
int cloned_a = 10;
int* ptr2 = &cloned_a;
이 상황에서는 ptr1과 ptr2의 메모리주소는 다르지만 그 값은 10으로 동일하다 이 상황에서 위의 상황처럼
*ptr2 = 7;
을 작성한다면 a값은 그대로 10이지만 cloned_a는 7로 변경되게 된다. 이러한 상황을 깊은 복사라고 한다.
1) 얕은 복사 : 실제 데이터가 아니라 주소만 복사되는 것, 변수명만 다르지 같은 주소를 공유하고 있는 상태
2) 깊은 복사 : 서로 다른 메모리 주소를 가지면서 해당 메모리 주소의 값은 같은 상태

포인터끼리는 -연산이 가능한데 그때의 결과는 떨어진 거리 즉 앞선 자료의 크기를 표시한다 예컨대
(char*)&my_info.name - (char*)&my_info.age;
를 연산하면 두 포인터 주소 간의 떨어진 거리 즉 my_info.name의 크기가 나오고 이는 각 크기가 32인 멤버를 갖고 있는 구조체이므로 64가 나오게 될 것이다.(음수던 양수던 간에) 그러면 각 값 또한 계산하면 64, 2, 4, 2가 나와야 할 것이다 결과를 볼까?

결괏값은 64, 4, 4, 4가 나왔다 분명 short자료형의 크기는 2바이트 여야 할 텐데 왜 4바이트씩 떨어져 있다고 나온 것일까? 이를 설명해 주는 것이 바로 바이트 정렬 요구사항이다.
바이트 정렬 요구사항은 시스템별로 메모리에 효율적으로 접근하기 위해서 n바이트 배수인 시작주소에서만 메모리 접근이 가능하게끔 한다. 대표적으로 x86시스템은 4바이트(== 워드크기) 경계에서 읽어오기 때문에 4바이트마다 정렬(aligned)되어 있다. 따라서 각 컴파일러가 알아서 각 멤버의 시작 위치를 그 경계에 맞춰준다. 그렇기 때문에 위의 결과가 나온 것이다. 왜? 크기가 2 바이트건 8 바이트건 4바이트 단위로 끊기 때문에 2바이트 다음으로 4바이트짜리 자료가 온다면 2바이트짜리를 채우고 남은 2바이트 공간은 비워두고 다음 값을 받아들이기 때문이다. 이를 패딩(padding)이라고 한다. 서로 다른 시스템에서 저장한 파일을 불러올 때 바이트 정렬 요구사항이 다르다면 데이터가 꼬일 수도 있다. 그럼 패딩을 줄이는 게 용이하지 않을까? 그렇기 위해서 다음 예제처럼 작성해 주면 된다.


아까는 80이 나왔던 그 예제인데 다른 점은 무엇일까? 각 멤버변수의 순서를 바꾸어 주었다. 순서대로 64바이트, 2바이트, 2바이트, 4바이트, 4바이트 순서로 선언되어 패딩의 작업이 필요 없이 깔끔하게 자료가 들어가게 되었다. 그렇기에 76바이트의 공간이 알차게 쓰인 것을 알 수 있다.
오늘은 구조체에 대해서 배울 수 있었다. 복잡하고 난해한 내용이라도 다시 한번 회독하면서 내것으로 만들면 이후의 프로젝트에서 잘 활용할 수 있을것이다.
'unreal 5기' 카테고리의 다른 글
| 250826 언리얼엔진 본캠프 16일차 템플릿 (1) | 2025.08.26 |
|---|---|
| 250822 언리얼엔진 본캠프 15일차 클래스와 객체지향적 프로그래밍 (2) | 2025.08.25 |
| 250820 언리얼엔진 본캠프 12일차 STL 기초 (0) | 2025.08.20 |
| 250819 언리얼엔진 본캠프 11일차 포인터 (2) (0) | 2025.08.19 |
| 250818 언리얼엔진 본캠프 10일차 포인터 (1) (1) | 2025.08.18 |