상세 컨텐츠

본문 제목

[C++] Struct (구조체)

C++

by 이나시오- 2024. 3. 12. 16:04

본문

  Struct(구조체)는 사용자 정의 자료형이라고 할 수 있다. 사용자가 정의한대로 관련 있는 여러 가지 데이터를 모아두는 자료형으로서 사용된다. 이번에 구조체에 대해 공부하면서 특이한 점을 알게 되어서 기록해두려고 작성하게 되었다.

 

           구조체의 정의, 선언, 초기화    

  구조체는 C에서부터 존재했던 내용으로, C에서 구조체를 정의하고 선언하는 방식과 C++에서 구조체를 정의하고 선언하는 방식이 조금 다르다고 한다. (물론 C의 방법도 C++에서 그대로 사용할 수 있다)

 

  C에서 구조체를 정의하고 사용하는 방법

방법 1. 구조체를 정의하고, 구조체 타입 변수를 선언할 때 앞에 struct 키워드를 붙여야 한다. (C)

방법 2. 임시 구조체를 정의함과 동시에 typedef를 이용해 MyStruct2라는 이름의 자료형으로 선언해서 자료형처럼 사용할 수 있다. 방법 1과 다르게 변수를 선언할 때 struct 키워드가 필요 없다 (C)

// 방법 1.
struct MyStruct1
{
    int m_iVal;
};

// 방법 2.
typedef struct
{
    int m_iVal;
} MyStruct2;


int main()
{
    // 방법 1.
    struct MyStruct1 my1;
    
    // 방법 2.
    MyStruct2 my2;

    return 0;
}

 

  C++에서는 두 방법 모두 사용할 수 있고, 방법 1에서 struct 키워드를 붙이지 않고도 구조체 타입 변수를 선언할 수 있다.

 

  이 때 구조체 안에 선언된 변수를 그 구조체의 member(멤버)라고 한다. 구조체는 다양한 변수를 멤버로 가질 수 있으며, 이들은 메모리 상에서 연속적으로 할당되며 코드에서 선언된 순서에 따라 배치된다. 따라서, 구조체 변수를 초기화할 때, 그 순서를 고려해서 초기화 할 수 있다.

struct MyStruct
{
    int m_iVal;
    float m_fVal;
    double m_dVal;
};



int main()
{
    // 구조체 변수의 초기화
    MyStruct my = {100,3.14f,31.41592};

    return 0;
}

 

  위와 같이 초기화를 하는 경우, 멤버의 순서대로 m_iVal에 100, m_fVal에 3.14f, m_dVal에 31.41592가 할당된다. {} 안에 아무것도 작성하지 않으면 자동으로 0을 값으로 할당해준다. 예를 들어 { }로 초기화 했다면 모든 멤버에 0이 할당되고, {100, }과 같이 초기화 하면 작성하지 않은 나머지 부분에 해당하는 멤버들이 0으로 값이 할당된다.

 

           구조체 멤버에 접근하기    

  구조체에 정의된 멤버에 접근할 수 있다. 기본적으로 구조체 변수 뒤에 .을 쓴 뒤 멤버의 이름을 써서 접근할 수 있다. 아래의 my.m_iVal 같은 경우로, my 변수안의 m_iVal에 접근하는 것이다. 또한, 구조체는 자료형과 같이 포인터를 이용해서 접근할 수도 있다. 구조체의 주소를 포인터 변수에 할당하면 구조체의 시작 주소를 알게 되고, *를 이용해 역참조 한 뒤 .을 이용해 멤버에도 접근할 수 있다 (방법 1). 이 때 방법 1처럼 작성하는 것이 번거롭다면 방법 2처럼 ->을 통해 접근할 수도 있다. 두 표현은 동치이다.

struct MyStruct
{
    int m_iVal;
    float m_fVal;
    double m_dVal;
};



int main()
{
    MyStruct my = {100,3.14f,31.41592};
    
    my.m_iVal = 200;
    
    MyStruct* pMy = &my;
    
    (*my).m_iVal = 300;	// 방법 1
    my->m_iVal = 400;	// 방법 2

    return 0;
}

 

           구조체의 크기    

  구조체 타입은 멤버들의 순서에 따라 메모리에 연속적으로 할당된 것으로 인식한다. 따라서 구조체 타입의 크기는 멤버들의 크기에 따라 결정된다. 이 때 직관적으로 생각하면 멤버들의 크기를 다 더하면 구조체의 크기가 될 것으로 예상되지만 실제로는 그렇지 않다. 구조체의 크기는 가장 크기가 큰 멤버의 크기를 한 단위로 하여 구성된다. 즉, 위의 경우 int, float, double 형의 멤버를 순서대로 가지므로 4/4/8 byte의 메모리를 가지게 되는데, 실제로는 가장 큰 8byte 단위로 메모리가 구성되어 [4/4][8] byte와 같이 구성된다. 다른 예시로 char, int, long long을 멤버로 가지는 경우 [1/4/나머지 3][8] byte로 메모리가 구성되는 것이다. 이를 Byte Padding이라고 한다.

  Byte Padding이 일어나는 이유는 CPU가 명령어를 처리하는 단위와 관련이 있다. CPU가 한 번에 명령어를 처리하는 단위를 word라고 한다. x86 CPU는 32bit (4byte), x64 CPU는 64bit (8byte)가 1word에 해당한다. 64비트 운영체제라 가정했을 때, byte padding이 적용되지 않은 경우 아래와 같이 메모리가 할당된다. 이 때 long long 타입의 멤버에 접근하고 싶으면 CPU가 총 2회 메모리에 접근해야 된다. 반면 위 그림처럼 padding이 적용된 경우 1회만으로 long long 타입의 멤버를 읽을 수 있게 된다.

  하지만 위와 같이 사용자의 환경에 따라 알아서 byte padding이 일어나는 경우 네트워크 통신 과정에서 패킷 사이즈가 달라 값을 제대로 주고 받을 수 없는 문제가 발생할 수 있다고 한다. 이러한 경우 패딩 사이즈를 통일해줘야 하는데, C++에서 직접 padding 단위를 지정할 수 있다.

#pragma pack(push,1)

struct MyStruct1
{
    char m_cVal;
    int m_iVal;
    long long m_lVal;
};

#pragma pack (pop)

struct MyStruct2
{
    char m_cVal;
    int m_iVal;
    long long m_lVal;
};


int main()
{
    int a = sizeof(MyStruct1);	// 13byte
    int b = sizeof(MyStruct2);	// 16byte

    return 0;
}

 

  #pragma pack(push,1)을 하면 앞으로 정의하는 구조체들에 대해서 padding 사이즈를 1로 지정한다는 의미이다. 따라서 우리가 처음에 직관적으로 생각하던 것처럼 모든 멤버의 사이즈를 더한 것이 곧 구조체의 사이즈가 된다. #pragma pack(pop)을 하면 우리가 설정했던 1사이즈의 패딩을 취소하고 원래대로 돌아가는 것이다. 이렇게 특정 구조체만 padding 사이즈를 지정해 줄 것이 아니라면 처음부터 #pragma(1)만 작성해도 된다.

 

  네트워크 통신에 대한 내용은 잘 몰라서 더 자세히 작성할 수 없지만 나중에 공부하게 된다면 더 자세하게 알아보고 싶다.

 

 

내용  참고 : https://www.youtube.com/watch?v=aROgtACPjjg&ab_channel=NesoAcademy (Byte Padding)

 

'C++' 카테고리의 다른 글

[C++] 함수 객체 (Functor)  (0) 2024.08.08
[C++] 함수 포인터  (0) 2024.03.19
[C++] 포인터  (0) 2024.03.19
[C++] 변수의 종류와 메모리 영역  (0) 2024.03.01
[C++] 자료형 (Data Type)  (0) 2024.02.29

관련글 더보기